ci(release): auto-PR в ordinis-infra с новым image tag после release
Раньше release tag создавал v2.X.Y, build images, но values-prod.yaml в
ordinis-infra оставался указывать на старый tag (либо unset → fall to
:latest). Это создавало divergence:
- Auto deploy через trigger-deploy-prod использовал runtime
UPSTREAM_IMAGE_TAG = новый tag → правильный deploy.
- Manual deploy через infra repo (helm upgrade -f values-prod.yaml)
использовал static values → старый tag → split-brain ноды через
:latest cache (см. ordinis-infra MR !10 fix).
Новый job 'propose-prod-image-bump':
- Triggers только на tag pipeline (semver v*.*.*).
- Needs все docker-* jobs (images должны быть в registry).
- Clones ordinis-infra, патчит image.tag в values-prod.yaml через awk
с scope-tracking (только image: block, не writer/readApi/etc).
- Commit + push branch + создаёт MR через GitLab API.
- Reviewer approve → merge → infra pipeline auto-катит deploy-prod.
Git становится единственным источником истины: HEAD ordinis-infra main
всегда отражает что задеплоено на prod.
Setup: новая CI variable INFRA_PROJECT_TOKEN (group-level access token
scope: write_repository, api, role: Developer). Без неё job exits early
с подсказкой — pipeline не блокируется (allow_failure: true).
This commit is contained in:
+184
@@ -479,3 +479,187 @@ release:
|
|||||||
# Не блокирует следующие pipelines если release fail'нул (e.g. нет
|
# Не блокирует следующие pipelines если release fail'нул (e.g. нет
|
||||||
# релизных commits, нет GITLAB_TOKEN, transient network error).
|
# релизных commits, нет GITLAB_TOKEN, transient network error).
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
||||||
|
# =============================================================
|
||||||
|
# Auto-PR: bump prod image tag в ordinis-infra/values-prod.yaml.
|
||||||
|
# =============================================================
|
||||||
|
# Зачем. До этого job'а prod deploys работали через две runtime механики:
|
||||||
|
#
|
||||||
|
# 1. Auto path. Ordinis tag pipeline → trigger-deploy-prod (manual gate)
|
||||||
|
# → downstream infra pipeline с UPSTREAM_IMAGE_TAG=v2-16-0-<sha>
|
||||||
|
# → helm upgrade --set writer.tag=$UPSTREAM ... → деплой.
|
||||||
|
# 2. Manual path. Помержить что-то в infra repo → infra pipeline →
|
||||||
|
# deploy-prod job (manual) → helm upgrade БЕЗ --set tag override →
|
||||||
|
# использует значения из values-prod.yaml.
|
||||||
|
#
|
||||||
|
# Проблема. (1) и (2) использовали разные источники истины для image tag:
|
||||||
|
#
|
||||||
|
# - (1) — runtime var UPSTREAM_IMAGE_TAG от triggering pipeline.
|
||||||
|
# - (2) — статика в values-prod.yaml (раньше unset → fall to global
|
||||||
|
# image.tag=latest → IfNotPresent кэш → split-brain pods, см.
|
||||||
|
# ordinis-infra MR !10 fix).
|
||||||
|
#
|
||||||
|
# После любого release manual path возвращался к старому tag'у который
|
||||||
|
# зафиксирован в values-prod.yaml — что divergировало с реальностью на
|
||||||
|
# кластере. Disaster-recovery `helm upgrade ... -f values-prod.yaml`
|
||||||
|
# rolled back prod на несколько версий назад.
|
||||||
|
#
|
||||||
|
# Решение. На каждый успешный release tag ordinis CI открывает MR в
|
||||||
|
# ordinis-infra обновляющий `image.tag` в values-prod.yaml на новый
|
||||||
|
# immutable tag (e.g. v2-16-0-<sha>). MR — single review point: после
|
||||||
|
# approve и merge infra pipeline auto-катит этот же tag через deploy-prod
|
||||||
|
# (rules: changes: charts/**/*).
|
||||||
|
#
|
||||||
|
# Git становится единственным источником истины: HEAD ordinis-infra
|
||||||
|
# main всегда отражает что задеплоено на prod.
|
||||||
|
#
|
||||||
|
# Setup ONE-TIME (см. RELEASE.md):
|
||||||
|
# 1. Создать group-level Access Token в 2-6/2-6-4/terravault group:
|
||||||
|
# - role: Developer на ordinis-infra (363)
|
||||||
|
# - scopes: write_repository, api
|
||||||
|
# 2. Settings → CI/CD → Variables в ORDINIS project:
|
||||||
|
# INFRA_PROJECT_TOKEN = <token> (Masked, Protected)
|
||||||
|
#
|
||||||
|
# Trigger: только на semver tag (pipeline created semantic-release'ом).
|
||||||
|
# Allow_failure: true — manual deploy backup path остаётся если что.
|
||||||
|
propose-prod-image-bump:
|
||||||
|
stage: release
|
||||||
|
image: repo.nstart.cloud/library/alpine:3.20
|
||||||
|
needs:
|
||||||
|
- job: docker-app
|
||||||
|
artifacts: false
|
||||||
|
- job: docker-admin-ui
|
||||||
|
artifacts: false
|
||||||
|
- job: docker-read-api
|
||||||
|
artifacts: false
|
||||||
|
- job: docker-projection-writer
|
||||||
|
artifacts: false
|
||||||
|
- job: docker-migrations
|
||||||
|
artifacts: false
|
||||||
|
rules:
|
||||||
|
# Только на tag pipeline (semantic-release завершил → новый tag → новый
|
||||||
|
# pipeline на ref=tag). Images должны быть собраны в этом pipeline'е
|
||||||
|
# (см. needs выше). На main pipeline tag ещё не существует — нечего
|
||||||
|
# bump'ать.
|
||||||
|
- if: '$CI_COMMIT_TAG =~ /^v[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||||
|
when: on_success
|
||||||
|
variables:
|
||||||
|
INFRA_PROJECT_ID: "368"
|
||||||
|
INFRA_REPO_PATH: "2-6/2-6-4/terravault/ordinis-infra"
|
||||||
|
before_script:
|
||||||
|
- |
|
||||||
|
if [ -z "$INFRA_PROJECT_TOKEN" ]; then
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
echo "INFRA_PROJECT_TOKEN is empty — skipping auto-PR creation."
|
||||||
|
echo ""
|
||||||
|
echo "Setup (one-time):"
|
||||||
|
echo " 1. group 2-6/2-6-4/terravault → Settings → Access Tokens"
|
||||||
|
echo " scopes: write_repository + api, role: Developer"
|
||||||
|
echo " 2. ordinis project (367) → Settings → CI/CD → Variables"
|
||||||
|
echo " add INFRA_PROJECT_TOKEN = <token>"
|
||||||
|
echo " flags: Masked + Protected (tag pipelines is Protected ref)"
|
||||||
|
echo ""
|
||||||
|
echo "См. RELEASE.md для подробностей."
|
||||||
|
echo "═══════════════════════════════════════════════════════════════"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
- apk add --no-cache git curl jq
|
||||||
|
script:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_TAG="${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
|
||||||
|
VERSION="${CI_COMMIT_TAG}"
|
||||||
|
BRANCH="auto/bump-prod-image-${VERSION}"
|
||||||
|
INFRA_API="${CI_API_V4_URL}/projects/${INFRA_PROJECT_ID}"
|
||||||
|
|
||||||
|
echo "Tag: $VERSION"
|
||||||
|
echo "Image tag: $IMAGE_TAG"
|
||||||
|
echo "Target branch: $BRANCH"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Clone infra (shallow для скорости).
|
||||||
|
git clone --depth=10 --branch=main \
|
||||||
|
"https://oauth2:${INFRA_PROJECT_TOKEN}@${CI_SERVER_HOST}/${INFRA_REPO_PATH}.git" infra
|
||||||
|
cd infra
|
||||||
|
git config user.email "ci@ordinis.local"
|
||||||
|
git config user.name "ordinis-ci-bot"
|
||||||
|
|
||||||
|
# Branch может остаться от прошлой неудачной попытки — clean delete.
|
||||||
|
git push origin --delete "$BRANCH" 2>/dev/null || true
|
||||||
|
git checkout -b "$BRANCH"
|
||||||
|
|
||||||
|
# 2. Patch values-prod.yaml — заменяем image.tag.
|
||||||
|
# Используем awk c stateful scope-tracking: ищем `image:` block (top-level,
|
||||||
|
# без leading spaces), затем заменяем ` tag: ...` строку. Не трогаем
|
||||||
|
# writer.tag / readApi.tag / etc — у них другая indentation level.
|
||||||
|
awk -v new_tag="$IMAGE_TAG" '
|
||||||
|
BEGIN { in_image_block = 0; replaced = 0 }
|
||||||
|
/^image:[[:space:]]*$/ { in_image_block = 1; print; next }
|
||||||
|
# Top-level keys (no indent, ends with `:`) reset scope.
|
||||||
|
/^[a-zA-Z][a-zA-Z0-9_-]*:/ && !/^image:/ { in_image_block = 0; print; next }
|
||||||
|
# Заменяем ` tag:` ТОЛЬКО внутри image: block. 2 spaces indent.
|
||||||
|
in_image_block && /^ tag:/ {
|
||||||
|
print " tag: \"" new_tag "\""
|
||||||
|
replaced = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END { if (!replaced) { print "ERROR: image.tag не найдена в values-prod.yaml" > "/dev/stderr"; exit 1 } }
|
||||||
|
' charts/ordinis/values-prod.yaml > /tmp/values-prod.new
|
||||||
|
mv /tmp/values-prod.new charts/ordinis/values-prod.yaml
|
||||||
|
|
||||||
|
# 3. Diff sanity: changed только эта строка.
|
||||||
|
if ! git diff --quiet charts/ordinis/values-prod.yaml; then
|
||||||
|
echo "=== Diff ==="
|
||||||
|
git diff charts/ordinis/values-prod.yaml
|
||||||
|
else
|
||||||
|
echo "INFO: tag уже $IMAGE_TAG в values-prod.yaml — no-op exit."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Commit + push branch.
|
||||||
|
git add charts/ordinis/values-prod.yaml
|
||||||
|
git commit -m "chore(release): bump prod image к ${IMAGE_TAG}
|
||||||
|
|
||||||
|
Auto-generated ordinis-CI release pipeline.
|
||||||
|
|
||||||
|
Source: ${CI_PROJECT_URL}/-/commit/${CI_COMMIT_SHA}
|
||||||
|
Pipeline: ${CI_PIPELINE_URL}
|
||||||
|
Tag: ${VERSION}
|
||||||
|
"
|
||||||
|
git push origin "$BRANCH"
|
||||||
|
|
||||||
|
# 5. Create MR через GitLab API.
|
||||||
|
# description через --data-urlencode сохраняет переносы строк.
|
||||||
|
MR_DESC="Auto-generated ordinis CI release pipeline после semantic-release ${VERSION}.
|
||||||
|
|
||||||
|
**Что меняется:** \`image.tag\` в \`charts/ordinis/values-prod.yaml\` → \`${IMAGE_TAG}\` (immutable SHA tag, гарантирует что ноды pull'нут именно этот digest).
|
||||||
|
|
||||||
|
**Source:**
|
||||||
|
- Ordinis commit: ${CI_PROJECT_URL}/-/commit/${CI_COMMIT_SHA}
|
||||||
|
- Tag pipeline: ${CI_PIPELINE_URL}
|
||||||
|
|
||||||
|
**Deploy после merge:** infra-pipeline auto-катит helm upgrade в \`ordinis-prod\` namespace через manual-gated \`deploy-prod\` job (как обычно).
|
||||||
|
|
||||||
|
**Verify checklist:**
|
||||||
|
- [ ] CI 'helm-lint' pipeline зелёный (validate charts)
|
||||||
|
- [ ] После merge play 'deploy-prod' job
|
||||||
|
- [ ] \`curl /api/v1/version\` показывает \`commit: ${CI_COMMIT_SHORT_SHA}\` и \`tag: ${VERSION}\`
|
||||||
|
- [ ] Pods Running 2/2 на обоих writer replicas
|
||||||
|
- [ ] Все 5 deployment'ов на одном digest (no split-brain)"
|
||||||
|
|
||||||
|
curl -sS --fail \
|
||||||
|
--request POST \
|
||||||
|
--header "PRIVATE-TOKEN: ${INFRA_PROJECT_TOKEN}" \
|
||||||
|
--data-urlencode "source_branch=${BRANCH}" \
|
||||||
|
--data-urlencode "target_branch=main" \
|
||||||
|
--data-urlencode "title=chore(release): bump prod image к ${VERSION}" \
|
||||||
|
--data-urlencode "description=${MR_DESC}" \
|
||||||
|
--data-urlencode "remove_source_branch=true" \
|
||||||
|
--data-urlencode "labels=release,auto-generated" \
|
||||||
|
"${INFRA_API}/merge_requests" \
|
||||||
|
| jq -r '"MR создан: " + .web_url'
|
||||||
|
# Не блокирует tag pipeline если auto-PR не получился (token отсутствует,
|
||||||
|
# transient API error и т.п.). Manual MR в infra остаётся всегда доступен.
|
||||||
|
allow_failure: true
|
||||||
|
|||||||
+25
@@ -26,6 +26,31 @@ human кликает Deploy.
|
|||||||
|
|
||||||
После этого release job в `.gitlab-ci.yml` будет работать.
|
После этого release job в `.gitlab-ci.yml` будет работать.
|
||||||
|
|
||||||
|
### Auto-PR в ordinis-infra (опционально)
|
||||||
|
|
||||||
|
`propose-prod-image-bump` job открывает MR в `ordinis-infra` после каждого
|
||||||
|
release tag — bump'ит `image.tag` в `values-prod.yaml` на свежий immutable
|
||||||
|
SHA tag (e.g. `v2-16-0-abc12345`). Чинит historical bug когда manual deploys
|
||||||
|
через infra repo получали `:latest` (stale cache → split-brain pods).
|
||||||
|
|
||||||
|
Setup:
|
||||||
|
|
||||||
|
3. **Create Group Access Token** в `2-6/2-6-4/terravault`:
|
||||||
|
- `Group → Settings → Access Tokens → Add new token`
|
||||||
|
- Name: `ordinis-ci-infra-bump`
|
||||||
|
- Role: `Developer`
|
||||||
|
- Scopes: `write_repository`, `api`
|
||||||
|
- Expiry: 1 год
|
||||||
|
|
||||||
|
4. **Add CI variable в ordinis project (367)**:
|
||||||
|
- Key: `INFRA_PROJECT_TOKEN`
|
||||||
|
- Value: (token из шага 3)
|
||||||
|
- Flags: `Masked` ✅, `Protected` ✅
|
||||||
|
|
||||||
|
Без этой переменной job exits early (`allow_failure: true`), tag pipeline
|
||||||
|
не блокируется. Manual MR в `ordinis-infra` остаётся всегда доступен как
|
||||||
|
fallback.
|
||||||
|
|
||||||
## Commit conventions
|
## Commit conventions
|
||||||
|
|
||||||
Conventional Commits (https://www.conventionalcommits.org/):
|
Conventional Commits (https://www.conventionalcommits.org/):
|
||||||
|
|||||||
Reference in New Issue
Block a user