مستندات Infynexراهنماهای پلتفرم، DevOps و انتشار
مستندات / عملیات انتشار / راهنمای انتشار نسخه
عملیات انتشار

راهنمای انتشار نسخه

فرایند RC تا Stable/Patch، Promote، Finalize، چک‌لیست‌ها و عیب‌یابی انتشار.

راهنمای کامل انتشار نسخه در پلتفرم Infynex

از اولین Release Candidate تا نسخهٔ پایدار، نسخهٔ وصله (Patch)، Backport و Changelog؛ همراه با مثال، دیاگرام و چک‌لیست.

مخزن Git: git.infynex.irسازمان: infynex-ioمثال اصلی: v1.7.0
با تغییر این دو مقدار، همهٔ مثال‌های سند به‌روز می‌شوند.

۱. این راهنما برای چیست؟

این سند هم طراحی فرایند (مدل انتشار چگونه کار می‌کند) و هم دفترچهٔ عملیاتی (دقیقاً چه کاری انجام بدهیم، چه چیزی ممکن است خراب شود و چه چیزی را باید بررسی کنیم) برای انتشار نسخه‌های پلتفرم Infynex است. فرایند انتشار Infynex مرحله‌ای و کنترل‌شده است تا هم پایداری حفظ شود و هم توسعهٔ نسخهٔ بعدی هیچ‌وقت متوقف نماند.

در موارد زیر سراغ این سند بیایید:

  • انتشار نسخهٔ پیش‌انتشار: vX.Y.0-rc.N (یا -alpha.N و -beta.N)
  • انتشار نسخهٔ پایدار: vX.Y.0
  • انتشار نسخهٔ وصله: vX.Y.Z با Z > 0
  • بررسی و انتخاب Cherry-pickها پیش از Patch
  • عیب‌یابی رفتار CI هنگام Push شدن یک Tag
  • بازبینی Pull Request انتشار (Promote PR)
آدرس‌ها در این سند سرور Git سازمان: https://git.infynex.ir — مخزن‌های سورس زیر https://git.infynex.ir/infynex-io/ قرار دارند. در مثال‌ها مخزن اصلی پلتفرم را https://git.infynex.ir/infynex-io/infynex فرض کرده‌ایم؛ اگر نام مخزن شما فرق دارد آن را در کادر بالای صفحه تغییر دهید.

واژه‌نامه

اصطلاحمعنی
RC (Release Candidate)نسخهٔ نامزد انتشار، مثلاً v1.7.0-rc.1؛ نسخه‌ای که برای آخرین مرحلهٔ تست منتشر می‌شود و در صورت سالم بودن به نسخهٔ پایدار ارتقا می‌یابد.
Freeze (انجماد)لحظه‌ای که اولین RC ساخته می‌شود و محتوای آن نسخه بسته می‌شود. از آن پس فقط اصلاحاتِ Cherry-pick‌شده وارد نسخه می‌شوند.
شاخهٔ نگهداری (Maintenance branch)شاخهٔ دائمی هر خط نسخه، مثل release-1.7. تمام RCهای بعدی، نسخهٔ پایدار و Patchهای آن خط از همین شاخه می‌آیند.
شاخهٔ مرحله‌ای (Staging)شاخه‌های موقتی که CI می‌سازد: release-1.7.0-rc.N (نتیجهٔ Build هر RC با Digestهای ثابت) و release-1.7.0 (شاخهٔ Promote PR).
Promoteارتقای یک RC تست‌شده به نسخهٔ پایدار، بدون Build دوباره؛ همان Imageهای RC با Digest یکسان فقط Tag پایدار می‌گیرند.
Finalizeمرحلهٔ بعد از Merge شدن Promote PR که تمام کارهای قابل‌مشاهده برای مصرف‌کننده را انجام می‌دهد (Tag پایدار، Release، Retag، Chart).
Digestهش sha256:… یک Image؛ شناسهٔ تغییرناپذیر محتوا. Tag می‌تواند جابه‌جا شود، Digest نه.
Cherry-pick / Backportانتقال یک Commit مشخص از شاخه‌ای به شاخهٔ دیگر (معمولاً از main به release-X.Y).
Write-onceTag پس از ساخته شدن هرگز جابه‌جا یا حذف نمی‌شود.
CI Appهویت خودکار (Bot/Token) که Tagها و PRهای انتشار را می‌سازد؛ در این سند با نام نمونهٔ infynex-ci[bot] آمده است.

۲. نمای کلی مدل انتشار

سه نوع انتشار داریم:

نوعمثالتوضیح
RCv1.7.0-rc.1نسخهٔ پیش‌انتشار برای تست و اعتبارسنجی نهایی. اولین RC از main ساخته می‌شود.
Regularv1.7.0نسخهٔ پایدار؛ همیشه از ارتقای یک RC حاصل می‌شود، نه Build دوباره و نه Tag دستی.
Patchv1.7.1فقط رفع باگ، پس از انتشار نسخهٔ پایدار، بر پایهٔ شاخهٔ release-1.7.

همهٔ این Tagها باید با عبارت منظم زیر تطبیق داشته باشند؛ Push شدن Tag همان چیزی است که کل خط لولهٔ CI را فعال می‌کند:

^v\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$

چهار قاعدهٔ طلایی

  1. Tag پایدار را هرگز دستی نزنید. vX.Y.Z فقط توسط جریان Promote و روی Merge commit ساخته می‌شود.
  2. Tag پیش‌انتشار را هم دستی Push نکنید. از workflow با نام Cut Pre-release Tag استفاده کنید (دلیلش در بخش ۸ آمده است).
  3. اولین RC خط نسخه را منجمد می‌کند. از آن پس هر RC دیگر باید از release-X.Y بیاید و Dispatch از main رد می‌شود.
  4. همان بایت‌هایی که تست شدند منتشر می‌شوند. نسخهٔ پایدار همان Imageهای RC است؛ فقط با Digest دوباره Tag می‌خورد.
تفاوت با یک مدل ساده مثل main → develop → release در این مدل شاخهٔ develop نداریم. شاخهٔ main همیشه برای توسعهٔ نسخهٔ بعدی باز است و شاخهٔ release-X.Y از لحظهٔ Freeze ساخته می‌شود. Commit انتشار هرگز به main برنمی‌گردد.
flowchart TD
  A[main: feature development] --> B[Cut Pre-release Tag from main: v1.7.0-rc.1]
  B --> C[release-1.7 created = FREEZE]
  B --> D[tags.yaml: build images and run rc-e2e]
  C -.-> M[main reopens for next minor]
  D --> E{rc-e2e green?}
  E -- No --> F[cherry-pick fix onto release-1.7]
  F --> G[Cut Pre-release Tag from release-1.7: rc.N+1]
  G --> D
  E -- Yes --> H[promote-rc.yaml]
  H --> I[branch release-1.7.0 and Promote PR]
  I --> J[Review and merge - never squash]
  J --> K[Finalize: tag v1.7.0, release, retag by digest, installer chart]
  K --> L[Merge parked website docs PR and announce]
چرخهٔ کامل انتشار یک نسخهٔ Regular

۳. پیش‌نیازهای راه‌اندازی در git.infynex.ir

فقط داشتن سورس و فایل‌های workflow کافی نیست. پیش از اولین انتشار باید زیرساخت اطراف آن هم روی سرور داخلی آماده باشد.

موردچرا لازم استمثال برای Infynex
CI App / BotTag پیش‌انتشار، Tag پایدار، شاخه‌های Promote و PR را باید «هویت CI» بسازد. (Push با توکن پیش‌فرض Workflow، Workflow دیگری را فعال نمی‌کند.)infynex-ci[bot] با دسترسی نوشتن روی Tag/Branch/PR/Release
Secretsتوکن CI App و توکن نوشتن در رجیستریدر مخزن infynex-io/infynex در بخش Secrets
رجیستری Image / OCIمقصد Imageها، آرتیفکت packages و Chart نصب‌کنندهregistry.infynex.ir/infynex-io/…
Branch protectionروی main و release-*؛ Merge فقط با PR، بدون Squash برای Promote PR، و Statusهای اجباری (مثل E2E Tests)الگوی release-*
Tag protectionتغییرناپذیری Tagهای v* و فقط CI برای ساختن آن‌ها (بخش ۱۵)الگوی v*
Labelهابرچسب‌های release، backport، backport-previous، full-e2e، kind/bug، kind/feature، area/security، area/ciساخت یک‌باره در تنظیمات مخزن
Runner و محیط E2Erc-e2e مجموعهٔ کامل نصب را روی دیسک و Imageهای منتشرشده اجرا می‌کندRunner داخلی با دسترسی به رجیستری
مخزن مستندات (وب‌سایت)Promote یک PR برای «مرجع اپلیکیشن‌ها» در مخزن مستندات باز می‌کندinfynex-io/website (اگر ندارید، این Job غیرمسدودکننده است و ⚠️ می‌گذارد)
مسیر ماژول GoTag پایدار api/apps/v1alpha1/vX.Y.Z هم ساخته می‌شود؛ مسیر ماژول در go.mod باید با آدرس جدید هماهنگ باشدgit.infynex.ir/infynex-io/infynex/api/apps/v1alpha1
هشدار دربارهٔ سازگاری با سکوی Git این فرایند به قابلیت‌های سکوی GitHub (Actions، GitHub App، Rulesets، GHCR، دستور gh، Copilot برای Changelog) تکیه دارد. اگر git.infynex.ir سکوی دیگری است، برای هر مورد معادل بیابید (بخش ۱۷). قبل از اولین انتشار واقعی، کل مسیر را روی یک Tag آزمایشی تمرین کنید.
همهٔ فایل‌های workflow تمام workflowها زیر .github/workflows/ مخزن‌اند. فهرستشان در بخش ۸ است. اگر سرور شما پوشهٔ دیگری می‌خواند (مثلاً .gitea/workflows/) مسیرها را متناسب جابه‌جا کنید.

۴. مدل شاخه‌ها و Tagها

برای اولین انتشار v1.7.0 با یک شاخه شروع می‌کنید: main. هنوز هیچ Release داخلی منتشر نشده است.

infynex-io/infynex
└── main            ← تنها شاخه در ابتدا

در طول عمر نسخه، این شاخه‌ها و Tagها ساخته می‌شوند:

نوعنامچه کسی می‌سازد / چه زمانیماندگاری
Branchmainاز ابتدادائمی
Tagv1.7.0-rc.1workflow ‏Cut Pre-release Tag از mainدائمی، Write-once
Branchrelease-1.7همزمان با rc.1 (Freeze)دائمی (شاخهٔ نگهداری)
Branchrelease-1.7.0-rc.1CI پس از Build هر RC (Digestها را ثابت می‌کند)موقت/قابل بازنویسی
Tagv1.7.0-rc.2 و …Cut Pre-release Tag از release-1.7دائمی
Branchrelease-1.7.0promote-rc.yaml هنگام Promoteموقت؛ پس از Merge حذف می‌شود
Tagv1.7.0Finalize، روی Merge commitدائمی، Write-once
Tagapi/apps/v1alpha1/v1.7.0Finalize، روی همان Commitدائمی
Tagv1.7.1-rc.1Cut Pre-release Tag از release-1.7دائمی
Branchrelease-1.7.1Promote برای Patchموقت
Tagv1.7.1Finalizeدائمی
ساختار نهایی پیشنهادی
Branches
├── main                         ← توسعهٔ نسخهٔ بعدی
└── release-1.7                  ← نگهداری دائمی خط 1.7
    ├── RC:     v1.7.0-rc.1, rc.2, rc.3
    ├── Stable: v1.7.0
    └── Patch:  v1.7.1, v1.7.2, v1.7.3 …

خلاصهٔ مهم: main هرگز Commit انتشار را دریافت نمی‌کند؛ در لحظهٔ Freeze از خط نسخه جدا می‌شود و کارِ نسخهٔ بعدی را ادامه می‌دهد.

۵. انتشار نسخهٔ Regular (مثال: v1.7.0)

نسخهٔ v1.7.0 از یک RC که E2E را گذرانده ارتقا پیدا می‌کند. هرگز Tag دستی یا Build دوباره ندارد.

گام ۰: آمادگی

همهٔ قابلیت‌های اصلی نسخهٔ ۱.۷ باید در main Merge شده باشند.

main
 ├── Feature A
 ├── Feature B
 ├── Feature C
 ├── Bug Fix A
 └── Bug Fix B
  1. انتشار rc.1 از main و Freeze
    در مخزن، بخش Actions ← workflow ‏Cut Pre-release Tag ← Run workflow با شاخهٔ main و تگ v1.7.0-rc.1.
    # معادل خط فرمان (نام ورودی را از فایل cut-prerelease.yaml چک کنید)
    gh workflow run cut-prerelease.yaml --ref main -f tag=v1.7.0-rc.1
    نتیجه: Tag روی HEAD شاخهٔ main به‌عنوان CI App ساخته می‌شود، شاخهٔ release-1.7 در همان Commit ساخته می‌شود (Freeze)، و سپس tags.yaml اجرا می‌شود: Imageهای RC را Build می‌کند، پیش‌انتشار را منتشر می‌کند، شاخهٔ release-1.7.0-rc.1 را با Digestهای ثابت Push می‌کند و Job اجباریِ rc-e2e را اجرا می‌کند.
  2. پایش rc-e2e و چک‌لیست پیش‌انتشار
    صبر کنید rc-e2e سبز شود، سپس چک‌لیست پیش‌انتشار را کامل کنید. اگر آخرین اجرای tags.yaml نتوانست rc-e2e سبز بدهد، یک اجرای موفق دستی e2e-tag.yaml برای همان Tag هم مدرک معتبر است.
    همین حالا main برای نسخهٔ بعدی باز است؛ هر چیزی که آنجا Merge شود در این نسخه نیست. اصلاحات ۱.۷ را با Cherry-pick به release-1.7 ببرید و RC بعدی را با Dispatch از release-1.7 بسازید.
    اختیاری: Changelog زودتر وقتی یادداشت‌ها ارزش بازبینی دارند، دکمهٔ Generate RC Changelog (workflow ‏changelog-rc.yaml) را بزنید تا docs/changelogs/v1.7.0.md همین حالا روی شاخهٔ مرحله‌ای release-1.7.0-rc.N ذخیره شود و فرصت ویرایش دستی داشته باشید. نزدن آن چیزی را خراب نمی‌کند؛ Promote خودش Changelog را می‌سازد.
  3. Promote: اجرای promote-rc.yaml
    Actions ← promote-rc.yaml ← Run workflow با تگ RC سالم (مثلاً v1.7.0-rc.2).
    gh workflow run promote-rc.yaml -f tag=v1.7.0-rc.2
    Job ‏parse پیش از هر کاری E2E را تأیید می‌کند: یا rc-e2e موفق در آخرین اجرای tags.yaml همان تگ، یا یک اجرای موفق دستی e2e-tag.yaml. سپس:
    1. نسخهٔ RC را در Tagهای Image به 1.7.0 بازنویسی می‌کند (Digestها همان RC می‌مانند)، آن را Commit می‌کند، پوشهٔ packages/ را زیر یک Tag موقت promotion-* منتشر می‌کند و Commit دوم را که Digest آن Candidate را در Installer ثابت می‌کند می‌سازد و شاخهٔ release-1.7.0 را Push می‌کند.
    2. پیش‌نویس (Draft) Release ‏v1.7.0 را با Assetهای بازنویسی‌شده می‌سازد.
    3. Changelog را (موازی) تولید می‌کند — یا اگر دکمهٔ rc-time را زده‌اید، همان فایل را کپی می‌کند — و روی شاخهٔ release-1.7.0 Commit می‌کند. این بخش غیرمسدودکننده است.
    4. یک PR در مخزن مستندات (update-docs-v1.7.0) باز می‌کند. غیرمسدودکننده و تا انتشار Release نباید Merge شود.
    5. PR با عنوان chore(release): promote v1.7.0-rc.2 -> v1.7.0 را به شاخهٔ release-1.7 باز می‌کند، فقط با برچسب release.
    تا Merge هیچ نام پایداری ساخته نمی‌شود Promote فقط یک Tag موقتِ قابل‌مشاهده (promotion-*) می‌نویسد. v1.7.0 ساخته نمی‌شود، latest جابه‌جا نمی‌شود و Installer پایدار منتشر نمی‌شود؛ پس یک Promote رهاشده هیچ آسیبی نمی‌زند. PR هدفش release-1.7 است نه main.
  4. بازبینی و Merge شدن Promote PR
    PR را طبق چک‌لیست فاز ۴ بررسی کنید: خط «✅ RC full e2e was verified»، سبز بودن Verify release packages candidate، وجود Changelog و خط مستندات. سپس Merge کنید — Squash نکنید؛ Tag پایدار باید روی یک Merge commit واقعی بنشیند. شاخهٔ release-1.7.0 با Merge حذف می‌شود.
  5. Finalize (خودکار پس از Merge)
    workflow ‏pull-requests-release.yaml فعال می‌شود و این کارها را به ترتیب انجام می‌دهد:
    1. آرتیفکت packages را با Digest می‌کشد، با درخت Merge‌شده تطبیق می‌دهد، مطمئن می‌شود هیچ Tag مربوط به rc باقی نمانده و مجموعهٔ Digestهای کانتینر برابر RC اصلی است.
    2. Tag ‏v1.7.0 را روی Merge commit می‌سازد (Write-once).
    3. Tag ماژول Go یعنی api/apps/v1alpha1/v1.7.0 را روی همان Commit می‌سازد.
    4. مطمئن می‌شود release-1.7 در همان Commit هست (معمولاً بدون عمل، چون Freeze ساخته است).
    5. Release را از Draft به منتشرشده (latest) تبدیل می‌کند و بدنهٔ آن را از docs/changelogs/v1.7.0.md می‌گیرد.
    6. آرتیفکت packages و Imageهای RC را با Digest به v1.7.0 Retag می‌کند (بدون Build) و Chart نصب‌کننده را منتشر می‌کند؛ :latest فقط اگر این نسخه جدیدترین نسخهٔ پایدار باشد.
  6. مستندات و اطلاع‌رسانی
    پس از انتشار Release، PR مستنداتِ «پارک‌شده» را Merge کنید تا مسیر نسخه در سایت به v1.7.0 برگردد؛ سپس انتشار را اعلام کنید.
    قرارداد زمان Merge مستندات اگر PR مستندات را قبل از انتشار Release Merge کنید، سایت به نسخه‌ای اشاره می‌کند که مستندات و openapi.json آن هنوز روی Git نیست و همهٔ لینک‌های آن نسخه ۴۰۴ می‌شوند.

دیاگرام Gitها در هر مرحله

الف) rc.1 و Freeze؛ سپس Cherry-pick و rc.2

gitGraph
    commit id: "feature A"
    commit id: "feature B"
    commit id: "feature C" tag: "v1.7.0-rc.1"
    branch release-1.7
    checkout main
    commit id: "feature D (next minor)"
    checkout release-1.7
    commit id: "cherry-picked fix" tag: "v1.7.0-rc.2"
پس از Freeze، main آزاد است و فقط release-1.7 اصلاحات نسخه را می‌گیرد

ب) Promote و باز شدن PR

gitGraph
    commit id: "feature C" tag: "v1.7.0-rc.1"
    branch release-1.7
    checkout release-1.7
    commit id: "cherry-picked fix" tag: "v1.7.0-rc.2"
    branch release-1.7.0
    checkout release-1.7.0
    commit id: "Prepare release v1.7.0"
    commit id: "Pin promoted packages artifact"
    checkout release-1.7
    merge release-1.7.0 id: "Pull Request"
دو Commit روی شاخهٔ Promote: آماده‌سازی انتشار و ثابت‌کردن آرتیفکت packages

ج) پس از Merge: Tag نهایی

gitGraph
    commit id: "feature 2"
    commit id: "feature 3" tag: "v1.7.0-rc.1"
    branch release-1.7
    checkout main
    commit id: "feature 4 (next minor)"
    checkout release-1.7
    commit id: "cherry-picked fix" tag: "v1.7.0-rc.2"
    branch release-1.7.0
    checkout release-1.7.0
    commit id: "Prepare release v1.7.0"
    checkout release-1.7
    merge release-1.7.0 id: "Release v1.7.0" tag: "v1.7.0"
main هیچ‌وقت Commit انتشار را دریافت نمی‌کند

جزئیات Cherry-pick برای RC بعدی

فرض کنید در تست rc.1 باگی در Kube-OVN پیدا شد. ابتدا آن را در main اصلاح و Merge کنید، سپس به شاخهٔ نگهداری ببرید:

git fetch origin
git checkout release-1.7
git pull --ff-only
git cherry-pick -x -s <fix-commit-sha>
git push origin release-1.7        # یا از طریق PR، طبق Branch protection

سپس Cut Pre-release Tag را این بار با شاخهٔ release-1.7 و تگ v1.7.0-rc.2 اجرا کنید. اگر دوباره اصلاحی لازم شد: rc.3 و همین‌طور تا سبز شدن. هر RC یک نسخهٔ آزمایشی مستقل است و E2E خودش را دارد.

چرا rc.2 از main ساخته نمی‌شود؟ اگر rc.2 از نوک main ساخته شود هر چیزی که بعد از rc.1 Merge شده بی‌صدا وارد نسخه می‌شود و نسخهٔ منتشرشده می‌تواند کدی داشته باشد که هیچ RC‌ای آن را تأیید نکرده است. Freeze دقیقاً برای جلوگیری از همین است.
برگرداندن Freeze (تصمیم دستی) اگر rc.1 غیرقابل‌استفاده بود و باید خط نسخه از main دوباره بریده شود، ابتدا شاخهٔ release-1.7 را دستی حذف کنید؛ تمام مسیرهای دیگر بازگشت به Dispatch از main مسدود است. هیچ چیز این حذف را خودکار نمی‌کند و نباید هم بکند.

۶. انتشار نسخهٔ وصله (Patch)

Patch دقیقاً همان شکل نسخهٔ Regular را دارد؛ چون بعد از انتشار v1.7.0 همه‌چیز روی release-1.7 اتفاق می‌افتد. تفاوت فقط نقطهٔ شروع است: شاخهٔ نگهداری از قبل وجود دارد و RC از release-1.7 بریده می‌شود.

سناریو: v1.7.0 منتشر شده، توسعه ادامه دارد و روی main دو اصلاح (patch 1 و patch 2) برای قابلیت‌های موجود در ۱.۷.۰ داریم.

  1. Cherry-pick اصلاحات به release-1.7
    فقط اصلاحاتِ قابلیت‌هایی که در v1.7.0 بوده‌اند. می‌توان بلافاصله بعد از Merge در main یا درست قبل از انتشار انجام داد. برچسب backport این کار را خودکار می‌کند (بخش ۱۱).
  2. ساخت RC وصله
    Cut Pre-release Tag از شاخهٔ release-1.7 با تگ v1.7.1-rc.1. CI می‌سازد، release-1.7.1-rc.1 را Push می‌کند و rc-e2e اجباری را اجرا می‌کند.
  3. پایش rc-e2e + چک‌لیست (و در صورت تمایل Generate RC Changelog، که docs/changelogs/v1.7.1.md را می‌سازد).
  4. Promote
    promote-rc.yaml با تگ v1.7.1-rc.1 — همان پنج Job. PR مستندات برای Patch پوشهٔ موجود v1.7/ را درجا به‌روز می‌کند (چون Patch پوشهٔ next/ را ارتقا نمی‌دهد).
  5. بازبینی و Merge مطابق چک‌لیست فاز ۴؛ Squash نکنید.
  6. Finalize: تأیید آرتیفکت، Tag ‏v1.7.1، Tag ماژول Go، انتشار Release (فقط اگر جدیدترین نسخهٔ پایدار باشد latest می‌شود)، Retag با Digest و Chart.
  7. Merge مستندات پارک‌شده و اطلاع‌رسانی.
gitGraph
    commit id: "feature 3" tag: "v1.7.0-rc.1"
    branch release-1.7
    checkout release-1.7
    commit id: "Release v1.7.0" tag: "v1.7.0"
    checkout main
    commit id: "feature 4"
    commit id: "patch 1"
    commit id: "feature 5"
    commit id: "patch 2"
    checkout release-1.7
    cherry-pick id: "patch 1"
    cherry-pick id: "patch 2" tag: "v1.7.1-rc.1"
    branch release-1.7.1
    commit id: "Prepare release v1.7.1"
    checkout release-1.7
    merge release-1.7.1 id: "Pull request"
فقط patch 1 و patch 2 به خط ۱.۷ می‌روند؛ feature 4 و 5 نه
Tag نباید جابه‌جا شود اگر بعد از انتشار v1.7.1 باگ بحرانی پیدا شد، Tag را جابه‌جا نکنید؛ v1.7.2 را از همین مسیر منتشر کنید.

۷. Alpha و Beta و RC

نوعمثالکاربردPromote می‌شود؟E2E
Alphav1.7.0-alpha.1تست اولیهٔ قابلیت‌ها و بازخوردخیرفقط با دکمهٔ دستی e2e-tag.yaml
Betav1.7.0-beta.1نسخهٔ نسبتاً کامل برای بازخوردخیرفقط با دکمهٔ دستی
RCv1.7.0-rc.1اعتبارسنجی نهایی پیش از انتشاربله، تنها نوعی که وارد Promote می‌شوداجباری و خودکار (rc-e2e)

مسیر Alpha → Beta → RC → Stable الزامی نیست. اگر نسخه آمادهٔ تست نهایی است، مستقیم از rc.1 شروع کنید. Alpha/Beta ساخته و برای دانلود منتشر می‌شوند ولی هرگز به نسخهٔ پایدار ارتقا پیدا نمی‌کنند. هر دوی این‌ها هم با همان workflow ‏Cut Pre-release Tag ساخته می‌شوند.

۸. CI در جریان انتشار چه می‌کند؟

فرایند بالا با هفت workflow پیاده شده (به‌علاوهٔ changelog-generate.yaml که هستهٔ مشترک تولید Changelog است). دانستن اینکه کدام Job چه می‌کند عیب‌یابی را بسیار آسان می‌کند.

فایلراه‌اندازوظیفه
cut-prerelease.yamlدستی (از main یا release-X.Y)تنها نقطهٔ ورود برای ساخت Tag پیش‌انتشار. Tag را معتبرسنجی و به‌عنوان CI App (Write-once، روی نوک شاخه) Push می‌کند تا tags.yaml فعال شود. اولین vX.Y.0-rc.N شاخهٔ release-X.Y را هم می‌سازد. Tag پایدار هرگز اینجا ساخته نمی‌شود.
tags.yamlPush شدن Tag پیش‌انتشارprepare-release: Build و انتشار پیش‌انتشار و Push شاخهٔ مرحله‌ای. فقط برای -rc.N: Job اجباری rc-e2e (فراخوانی e2e-tag.yaml). Jobهای generate-changelog و update-website-docs فقط پشتیبان هستند.
e2e-tag.yamlفراخوانی از rc-e2e یا اجرای دستی برای Tag منتشرشدهنصب کامل، OpenAPI و مجموعهٔ Chainsaw را روی دیسک منتشرشده و درخت Digest‌دار اجرا می‌کند.
promote-rc.yamlدستیپنج Job: parse (اعتبارسنجی و مدرک E2E)، promote (بازنویسی، Candidate، Draft Release)، changelog، website-docs، open-pr. هیچ نام پایداری نمی‌سازد.
pull-requests.yamlPRCandidate را با درخت Merge احتمالی و آرتیفکت اصلی RC تطبیق می‌دهد تا Status اجباری E2E Tests سبز شود.
pull-requests-release.yamlMerge شدن Promote PRFinalize: تأیید دوباره، Tag پایدار و ماژول Go، انتشار Release با Changelog، Retag با Digest و Chart.
update-releasenotes.yamlPush به main که docs/changelogs/v*.md را تغییر دهدهمگام‌سازی بدنهٔ Release با Changelog (برای ویرایش‌های بعدی و همگام‌سازی دستی).
changelog-rc.yamlدستی، اختیاریChangelog را در زمان RC می‌سازد و روی شاخهٔ مرحله‌ای ذخیره می‌کند.
changelog-generate.yamlفراخوانی از دو مورد بالاهستهٔ مشترک تولید Changelog.
backport.yamlPR Merge‌شده روی main با برچسبساخت خودکار PR برای Cherry-pick (بخش ۱۱).
nightly.yaml، retention.yamlزمان‌بندیNightly و پاکسازی (بخش ۱۳).
چرا Tag را دستی Push نکنیم؟ دو دلیل: (۱) Tag که با توکن پیش‌فرض Workflow Push شود هیچ Workflow دیگری را فعال نمی‌کند؛ (۲) tags.yaml برای تگی که Base نداشته باشد خطا می‌دهد. workflow ‏Cut Pre-release Tag با هویت CI App و روی نوک شاخه Push می‌کند و هر دو مشکل را حل می‌کند؛ همچنین به مدیران اجازه می‌دهد ساخت Tagهای v* را فقط برای CI قفل کنند.
sequenceDiagram
    autonumber
    actor M as Maintainer
    participant CP as cut-prerelease.yaml
    participant T as tags.yaml
    participant E as e2e-tag.yaml
    participant P as promote-rc.yaml
    participant R as Promote PR
    participant F as pull-requests-release.yaml
    participant G as Registry
    M->>CP: dispatch (tag v1.7.0-rc.1, from main)
    CP->>CP: push tag, create release-1.7 (freeze)
    CP->>T: tag push triggers
    T->>G: build and publish rc images
    T->>E: rc-e2e (mandatory)
    E-->>T: green
    M->>P: dispatch (tag v1.7.0-rc.2)
    P->>P: parse: verify rc-e2e evidence
    P->>G: publish promotion candidate (temporary tag)
    P->>R: open PR release-1.7.0 into release-1.7
    M->>R: review and merge (no squash)
    R->>F: merged, label release
    F->>G: verify candidate by digest
    F->>F: create tag v1.7.0 on merge commit
    F->>G: retag by digest, publish installer chart
    F-->>M: GitHub Release published (latest)
توالی رویدادها از Dispatch تا انتشار

۹. فازهای خط لولهٔ انتشار

فاز ۱ — prepare-release (دروازهٔ سخت)

روی Push هر Tag RC: Imageها را Build می‌کند، Digestها را Commit می‌کند، Draft Release می‌سازد و شاخهٔ release-X.Y.Z-rc.N را Push می‌کند. Commit ‏«Prepare release vX.Y.Z» (نویسنده: CI Bot) فقط Digest و Tag Image است:

  • packages/core/{installer,platform,testing}/values.yaml
  • packages/system/*/values.yaml
  • packages/apps/kubernetes/images/*.tag
  • packages/system/dashboard/templates/configmap.yaml (مقدار $tenantText)

چیزهایی که عجیب به نظر می‌رسند اما عادی‌اند:

  • پکیج‌های بدون تغییر: وقتی Cache کامل بوده و Base Imageها جابه‌جا نشده‌اند Digest عیناً مثل قبل است. «بدون Diff» یعنی «Build نشده» نیست؛ اجرای مرحلهٔ Build را بررسی کنید.
  • تغییرات ناگهانی در Tagهای ubuntu-container-disk-*: منبع بالادستی یک هدف متحرک است.
  • تغییر رجیستری: اگر Imageای از رجیستری دیگر به رجیستری داخلی منتقل شده، بخش رجیستریِ Digest عوض می‌شود؛ این یک انتقال عمدی است.
  • targetVersion توسط PR انتشار تغییر نمی‌کند. این عدد (Migration) پیش‌تر در یک Commit قابلیت بالا می‌رود؛ PR انتشار فقط Digest ایمیج Migrationها را ثابت می‌کند.

E2E اجباری RC پیش از Promote

prepare-release باید تمام شود، بعد rc-e2e مجموعهٔ کامل را روی دیسک و درخت Digest‌دار منتشرشده اجرا می‌کند؛ یعنی همان چیزی که Promote دوباره استفاده می‌کند. promote-rc.yaml آخرین اجرای tags.yaml را برای همان Tag بررسی می‌کند و فقط وقتی می‌پذیرد که Job E2E موفق باشد. مسیر جایگزین: یک اجرای دستی موفقِ نگه‌داشته‌شدهٔ e2e-tag.yaml که نام Job آن E2E vX.Y.Z-rc.N (full suite) باشد.

گیت بسته می‌شود، نه باز اگر Retention اجراها را پاک کرده باشد یا نام Job عوض شده باشد، Gate شکست می‌خورد و از شما می‌خواهد دکمهٔ E2E Release Tag را دوباره بزنید. skip_e2e_gate=true فقط یک «اضطراری» است: هشدار بلند می‌نویسد و در بدنهٔ PR خط ⚠️ می‌گذارد؛ مدرک جایگزین نیست.

فاز ۲ — Changelog

Changelog نسخه توسط Job ‏changelog در promote-rc.yaml ساخته و روی شاخهٔ release-X.Y.Z Commit می‌شود؛ پس در PR بازبینی می‌شود و لحظهٔ انتشار روی شاخهٔ پایه است. Finalize همان فایل را عیناً بدنهٔ Release می‌کند. تولید آن عمداً غیرمسدودکننده است: قطع شدن سرویس هوش مصنوعی یا اتمام سهمیه نباید انتشار را متوقف کند. در این حالت بدنهٔ PR خط ⚠️ «No changelog in this PR» دارد.

راه‌های بازیابی به ترتیب ترجیح:

  1. فایل docs/changelogs/vX.Y.Z.md را دستی روی شاخهٔ release-X.Y.Z Commit کنید. (Promote دوباره آن را حفظ می‌کند.)
  2. promote-rc.yaml را دوباره Dispatch کنید؛ هر چیزی که از قبل روی شاخه باشد برنده است.
  3. بدون آن Merge کنید و بگذارید tags.yaml::generate-changelog (پشتیبان) بعداً یک PR Changelog روی main باز کند.

شکست‌های شناخته‌شده: اتمام سهمیهٔ توکن هوش مصنوعی (خطای 402)، Timeout سی‌دقیقه‌ای، و خروجی ناقص که hack/validate-changelog.sh آن را می‌گیرد (باید لینک Release، لینک Compare به همین نسخه و حداقل یک بخش ## داشته باشد؛ Patch کوتاه هم مجاز است).

چرا پشتیبان همیشه لازم است؟ چون Promote PR همیشه به release-X.Y می‌رود، فایل Changelog هرگز مستقیم به main نمی‌رسد. Job پشتیبان generate-changelog همان فایل بازبینی‌شده را عیناً از Commit تگ به main منتقل می‌کند (بدون تولید دوباره).

فاز ۳ — update-website-docs (پشتیبان)

PR مستندات به‌طور معمول زودتر، در زمان Promote، توسط Job ‏website-docs باز می‌شود و محتوا را از شاخهٔ مرحله‌ای release-X.Y.Z (با متغیر FETCH_REF) می‌خواند. پیش از ارتقای next/ به vX.Y/ نسخه‌های ثابت‌شدهٔ Talos و پلتفرم را از همان شاخه تازه می‌کند. غیرمسدودکننده است. Job زمان-Tag فقط پشتیبان است و اگر کار انجام شده باشد با «No changes to commit» رد می‌شود. اگر کسی Makefile مستندات را طوری عوض کند که جای دیگری بنویسد، لیست git add باید در هر دو Job گسترش یابد.

فاز ۴ — بازبینی و Merge شدن Promote PR

چک‌لیست بازبین برای PR با عنوان chore(release): promote vX.Y.Z-rc.N -> vX.Y.Z:

  • ☐ بدنهٔ PR می‌گوید «✅ RC full e2e was verified». اگر Gate دور زده شده، پذیرش صریح مسئول انتشار یا افزودن برچسب full-e2e و انتظار برای نتیجه لازم است.
  • ☐ Verify release packages candidate سبز است. (بعد از تولید PR، packages/ را ویرایش نکنید؛ اگر Guard انحراف نشان داد، Promote را دوباره Dispatch کنید.)
  • ☐ خط مستندات در PR هست: اگر ✅، PR مستندات باز و پارک‌شده است؛ اگر ⚠️، پشتیبان بعد از Finalize پر می‌کند.
  • ☐ Diff فقط Digest و Tag Image است، چیز دیگری نیست.
  • ☐ هیچ انحراف پیکربندی ناشناخته‌ای نیست (فایل Values غیرمنتظره).
  • ☐ اگر Chartی عوض شده، Digest جدید Pull می‌شود: crane manifest <registry>/<name>@<digest> باید جواب بدهد.
  • ☐ آرتیفکت‌های Draft Release موجود و به تعداد مورد انتظارند.
  • ☐ docs/changelogs/vX.Y.Z.md در PR هست، محتوا دارد و با بازهٔ Commitها می‌خواند.
تغییر packages/ روی شاخهٔ پایه در حین باز بودن PR وقتی شاخهٔ پایه جلو می‌رود هیچ رویداد pull_request دوباره اجرا نمی‌شود، پس تغییر packages/ بعدی دوباره تأیید نمی‌شود. Finalize روی Merge commit واقعی دوباره بررسی می‌کند اما بعد از Merge؛ پس تا باز بودن Promote PR از Merge تغییرات packages/ به شاخهٔ پایه خودداری کنید.

فاز ۵ — Finalize

روی Merge یک PR که (۱) Merge شده، (۲) برچسب release دارد و (۳) نویسندهٔ آن CI Bot است اجرا می‌شود؛ شاخهٔ سر باید الگوی release-X.Y.Z[-suffix] داشته باشد. شرط نویسنده جلوی این سوءاستفاده را می‌گیرد که کسی شاخه‌ای با این نام بسازد، برچسب بزند و Merge کند. مراحل در بخش ۵ (گام ۵) آمده‌اند. نکات کلیدی:

  • Checkout با persist-credentials: false انجام می‌شود تا Push با CI App باشد و tags.yaml فعال شود.
  • make_latest فقط برای جدیدترین نسخهٔ پایدار منتشرشده درست است؛ Patch قدیمی‌تر latest را پایین نمی‌آورد. :latest Imageها و Chart از همین تصمیم پیروی می‌کنند.
  • Chart نصب‌کننده با platformVersion تنظیم‌شده در Values پیش‌فرض بسته‌بندی می‌شود تا helm --install --version X.Y.Z نسخهٔ پایدار را گزارش کند.

فاز ۶ — update-releasenotes

روی Push به main که docs/changelogs/v*.md را تغییر دهد، بدنهٔ Release متناظر را در صورت اختلاف به‌روز می‌کند. دیگر مسیر بحرانی نیست؛ ویرایش‌های بعدی، PR پشتیبان Changelog و همگام‌سازی دستی (workflow_dispatch) را پوشش می‌دهد.

۱۰. چک‌لیست پیش از انتشار

برای RCها و نسخهٔ نهایی، قبل از Merge کردن Promote PR. هر مورد وجود دارد چون یک بار نبودنش کاربران را آزرده کرده است.

  • ☐ RC منتشرشده E2E کامل را گذرانده («✅ RC full e2e was verified»). PR عادی E2E اجرا نمی‌کند؛ فقط در صورت نیاز به تکرار full-e2e بزنید و خط ⚠️ را یک تصمیم ریسک آگاهانه بدانید.
  • ☐ ارتقا از Patch قبلی (vX.Y.(Z-1)) روی کلاستر واقعی: helm upgrade موفق، همهٔ HelmReleaseها Ready، بدون رد شدن CRD Schema.
  • ☐ ارتقا از Minor قبلی برای RC و نسخه‌های Minor.
  • ☐ حداقل یک Tenant با publishing.ingressName != tenant-root و صدور واقعی گواهی در Let’s Encrypt staging.
  • ☐ Apply-mutate-reapply روی HelmRelease چتر Kubernetes (باگ‌های دور دوم Reconcile).
  • ☐ برای هر ارتقای منبع CRD (kubevirt، cert-manager، flux، cilium، kamaji): helm template | kubectl apply --dry-run=server روی CRDهای تازه‌نصب.
  • ☐ برای هر ارتقای flux / helm-controller / kubevirt-operator / cert-manager: بخش Breaking changes بالادستی را در بدنهٔ PR بچسبانید.
  • ☐ پس از ساخت Tag، اجرای Finalize را تماشا کنید. اگر با «Draft release for v… not found» شکست خورد، Release را دستی Publish کنید و باگ Workflow را ثبت کنید.
  • ☐ Changelog در PR هست و طبق بخش ۱۲ راستی‌آزمایی شده است.
  • ☐ PR مستندات تا انتشار پارک می‌ماند و بلافاصله بعد از Finalize Merge می‌شود.

۱۱. Backport و Cherry-pick

ربات Backport

workflow ‏backport.yaml (بر پایهٔ korthout/backport-action) روی PR Merge‌شده‌ای که به main رفته و یکی از برچسب‌های زیر را دارد اجرا می‌شود:

برچسبشاخهٔ مقصد
backportجدیدترین شاخهٔ موجود release-X.Y
backport-previousدومین شاخهٔ جدید release-X.Y

مقصد هنگام اجرا پویا حساب می‌شود: شاخه‌های مخزن فهرست می‌شوند، آنهایی که الگوی release-<major>.<minor> دارند نگه داشته می‌شوند و عددی نزولی مرتب می‌شوند (پس release-1.10 بالاتر از release-1.9 است). اگر فقط یک خط وجود داشته باشد و backport-previous بزنید، Job شکست می‌خورد.

در پنجرهٔ Freeze مقصد «جدیدترین» چیست؟ release-X.Y از زمان rc.1 (قبل از انتشار vX.Y.0) وجود دارد؛ پس تا انتشار، backport به نسخه‌ای می‌رود که در حال تثبیت است و backport-previous به آخرین نسخهٔ پایدار منتشرشده. برای رساندن اصلاح به خط‌های قدیمی‌تر در این بازه باید دستی Cherry-pick کنید.

عنوان PR ربات: [Backport release-X.Y] <عنوان اصلی>. برای جلوگیری از Backport بازگشتی، هر دو Job روی github.event.pull_request.base.ref == 'main' شرط دارند؛ PR Backport به release-X.Y می‌رود و هرگز شرط را برآورده نمی‌کند.

وقتی ربات شکست می‌خورد

  1. اجرای ناتمام قبلی را پیدا و پاک کنید، چون اجرای دوباره پشت آن صف می‌ماند. روی وضعیت فیلتر نکنید (اجرایی که منتظر Runner است in_progress نیست). تأیید کنید اجرا مال همان PR است.
    gh run list --workflow backport.yaml --branch <head-branch-of-the-PR>
  2. برچسب Backport را بردارید و دوباره اضافه کنید؛ رویداد Label تنها Trigger دیگر است.
  3. Cherry-pick متعارض یک PR پیش‌نویس (Draft) با Diff تعارض و کامنت ربات می‌سازد. یا در همان شاخه حل و Undraft کنید، یا دستی:
git checkout release-X.Y
git cherry-pick -x -s <commit-sha>
# حل تعارض‌ها
git commit -s
git push origin release-X.Y   # یا Push به یک شاخهٔ جدید و باز کردن PR

غربال‌گری Cherry-pick پیش از Patch

# 1. PRهای دارای برچسب که هنوز روی release-X.Y نیستند
gh pr list --search "is:merged label:backport" --limit 100
gh pr list --search "is:merged label:backport-previous" --limit 100

# 2. Commitهای main از زمان انشعاب که روی release-X.Y نیستند
git merge-base origin/main origin/release-X.Y
git log <base>..origin/main --grep="(#" --oneline

# 3. PRهای باز که شاید قبل از برش نیاز به برچسب دارند
gh pr list --state open --base main --label kind/bug
شامل شود ✅شامل نشود ❌
  • kind/bug به‌ویژه مخرب (از دست رفتن داده، CrashLoop، OOM، Eviction)
  • area/security / CVE
  • اصلاح‌های Chart با شعاع اثر محدود (منابع یک Operator)
  • ارتقای وابستگی مطابق سیاست خط نسخه (فقط Patch داخل همان خط، نه Minor)
  • kind/feature حتی با lgtm؛ Patch وسیلهٔ تحویل قابلیت نیست
  • تغییرات فقط CI (area/ci) و زیرساخت تست
  • تغییرات فقط مستندات (خودکار از مسیر مستندات می‌آیند)
  • ارتقاهای حجیم چندبسته‌ای وابستگی (Dependabot Go)
  • PRهای Breaking (!) یا ترکیب feat با fix

موارد مرزی: بازآرایی بزرگ با lgtm و محتوای واضح رفع باگ ← رفع باگ را در یک Commit جدا استخراج و فقط آن را Backport کنید. PR با هر دو برچسب kind/bug و kind/feature ← تقسیمش کنید.

flowchart TD
  S[Merged PR on main] --> Q{Type?}
  Q -- kind/bug destructive or CVE --> Y[Add label backport]
  Q -- narrow chart fix --> Y
  Q -- patch-level dependency bump --> Y
  Q -- kind/feature --> N[Skip]
  Q -- CI only or docs only --> N
  Q -- mixed feat and fix --> SP[Split the PR] --> Y
  Y --> B[backport.yaml opens PR to release-X.Y]
  B --> C{Conflict?}
  C -- No --> M[Merge backport PR]
  C -- Yes --> D[Draft PR: resolve or cherry-pick manually]
  D --> M
تصمیم‌گیری برای Backport

کدی که شمارندهٔ یکنواخت (Monotonic counter) را لمس می‌کند

مثال کلاسیک Migrationهاست. اسکریپت packages/core/platform/images/migrations/run-migrations.sh یک شمارندهٔ خطی است: seq $CURRENT_VERSION $((TARGET_VERSION - 1)) را اجرا می‌کند و اگر CURRENT >= TARGET بود متوقف می‌شود. این یک تله برای ارتقای رو به جلوست:

  • main دارای targetVersion=10 و Migrationهای ۱ تا ۹ است.
  • release-1.7 زمانی بریده شد که targetVersion=5 بود (Migrationهای ۱ تا ۴).
  • یک Migration جدید را در شمارهٔ ۵ به release-1.7 Backport می‌کنید و ساده‌لوحانه targetVersion را ۵ ← ۶ می‌برید.
  • مشتری ۱.۷.x اجرا می‌کند و cozystack-version=6 را در کلاستر ثبت می‌کند (نام این کلید در کد فعلی است).
  • هنگام ارتقا به نسخهٔ بعدی (targetVersion=10) اجراکننده seq 6 9 را می‌رود و Migration شمارهٔ ۵ خودِ main هرگز اجرا نمی‌شود؛ آن شماره را Backport اشغال کرده است.

راه‌حل‌ها به ترتیب ترجیح:

  1. targetVersion را در Backport بالا نبرید. فقط فایل Migration را بفرستید و Guard سمت Chart را در شاخهٔ نگهداری شل کنید تا مقدار جدید بدون اجرای Migration کار کند.
  2. اگر ناچار به بالا بردن هستید، نسخهٔ Idempotent Migration رد‌شدهٔ main را در یک شمارهٔ بالاتر تکرار کنید.
  3. قبل از تصمیم، وضعیت شمارنده را بین شاخه‌ها ممیزی کنید: main، release-X.Y و release-X.(Y-1). اگر Backport در هر مسیر ارتقا پنجرهٔ پرش می‌سازد، گزینهٔ ۱ را انتخاب کنید.

این قاعده برای هر وضعیت شمارنده‌ای صدق می‌کند: نسخهٔ Schema، نسل Feature Flag و هر چیزی که «آیا X را قبلاً اجرا کرده‌ام؟» را با یک عدد صحیح تعیین می‌کند.

۱۲. Changelog

منبع حقیقت فرایند، سند docs/agents/changelog.md در مخزن است. پیش از تولید، آن را کامل بخوانید و فرایند را از روی Commitهای قبلی یا حافظه حدس نزنید. CI آن را از طریق changelog-generate.yaml اجرا می‌کند؛ محلی هم می‌توانید همان Prompt را با دنبال کردن سند اجرا کنید.

خطاهای رایج در Changelog

#خطاراه پیشگیری
۱Commit از خارج بازهٔ انتشار (اجرای git log <prev>..HEAD از main برای Patch)همیشه git log <prev>..<new_tag> را صریح بنویسید.
۲اصل و Backport به‌صورت دو ورودی جداهر Backport با اصل خودش در یک ورودی ادغام شود: (in #orig, backport #bp).
۳نویسندهٔ اشتباه PR (نویسندهٔ Squash commit همان کسی است که Merge زده)gh pr view <N> --json author --jq .author.login، نه git log --format=%an.
۴Patch جایگزین و Changelog کهنهTag Write-once است؛ کار Changelog به Patch بعدی منتقل می‌شود و فقط Deltaها اضافه/حذف می‌شوند.
۵شمارهٔ PR جابه‌جا در متن توصیفیاعتبارسنج فقط ورودی‌های فهرست را می‌بیند؛ هر #NNNN در متن را با gh pr view بررسی کنید.
۶ورودی ساختگیهر ورودی باید متناظر Commit در بازه باشد (اسکریپت پایین).
۷حساب‌های Bot در فهرست مشارکت‌کنندگان انسانیCI Bot، github-actions، dependabot، renovate و app/* فقط در تخصیص هر ورودی مجازند، نه در ## Contributors.
۸عنوان به‌جای توضیح تکرار شده«fix(foo): X: fix(foo): X» یعنی توضیح کاربرمحور نوشته نشده؛ جزئیات باید بگوید تغییر برای کاربر چه معنایی دارد.

راستی‌آزمایی پیش از انتشار Changelog

PREV=v1.7.0; NEW=v1.7.1

# A. هر ارتقای Chart در Changelog ذکر شده باشد
git diff --name-only $PREV..$NEW -- 'packages/*/charts/*/Chart.yaml' 'packages/*/*/charts/*/Chart.yaml' | while read f; do
  old=$(git show "$PREV:$f" 2>/dev/null | yq '.version // ""')
  new=$(git show "$NEW:$f"  2>/dev/null | yq '.version // ""')
  if [ "$old" != "$new" ]; then
    component=$(basename "$(dirname "$f")")
    grep -qi "$component" docs/changelogs/$NEW.md || echo "MISSING: $component $old -> $new ($f)"
  fi
done

# B. Pinهای خارج از Chart.yaml (Talos، K8s مستأجر، Imageهای Vendor‌شده)
for path in packages/core/talos/values.yaml packages/core/installer/values.yaml packages/apps/kubernetes/values.yaml; do
  git diff $PREV..$NEW -- "$path" | head -40
done
git diff --name-only $PREV..$NEW -- 'images/*/Dockerfile'

# C. هر #NNNN موجود و یک PR Merge‌شده است (نه Issue)
grep -oE '#[0-9]+' docs/changelogs/$NEW.md | sort -u | sed 's/#//' | xargs -P 20 -I{} sh -c '
  state=$(gh api /repos/infynex-io/infynex/issues/{} --jq ".pull_request.merged_at // \"ISSUE\"" 2>/dev/null)
  echo "#{} $state"' | grep -E 'ISSUE|null|^#[0-9]+ $'

# D. زنده بودن URLها
grep -oE 'https://[^)" ]+' docs/changelogs/$NEW.md | sort -u | xargs -P 10 -I{} sh -c '
  code=$(curl -s -o /dev/null -w "%{http_code}" {}); [ "$code" != "200" ] && echo "$code {}"'

# E. نویسندهٔ PRهای مخزن مستندات، نویسندهٔ PR است نه Commit
grep -oE 'infynex-io/website#[0-9]+' docs/changelogs/$NEW.md | sort -u | sed 's|infynex-io/website#||' | while read n; do
  echo "website#$n -> @$(gh pr view $n --repo infynex-io/website --json author --jq .author.login)"
done

# F. درخت کاری تمیز، جز خود Changelog
git status --porcelain | grep -v "docs/changelogs/$NEW.md"
معیار انتشار هر ارتقای Chart دیده شود، هیچ Issue به‌جای PR نقل نشود، هیچ URL ۴۰۴ نباشد، هر ورودی مخزن مستندات با gh pr view نسبت داده شده باشد و git status فقط Changelog را نشان دهد. اسکریپت‌ها از gh استفاده می‌کنند؛ اگر سکوی شما معادل ندارد، همان کار را با API سرور انجام دهید.

نکتهٔ URL در مستندات

مسیرهای وب‌سایت مستندات نسخه‌دار هستند. فقط /docs/vX.Y/foo/ و شکل بدون نسخهٔ استاندارد محتوا می‌دهند؛ مسیرهای حدسی مثل /docs/next/foo/ یا مسیر بدون Section خطای ۴۰۴ می‌دهند. Changelogهای تولیدشده با هوش مصنوعی اغلب شکل بدون نسخه را از خود اختراع می‌کنند؛ همیشه با حلقهٔ curl بالا بررسی کنید.

پوشش اجزای پلتفرم در Minor

برای یک Minor، ورودی‌ها باید نشان دهند ارتقای Chart بالادستی واقعاً چه چیزی آورده: قابلیت، Breaking change، رفع امنیتی. برای هر Chart.yaml ارتقایافته:

  1. فیلد sources: (معمولاً یک آدرس Git) را بخوانید.
  2. یادداشت‌های انتشار را فقط در بازهٔ old < x <= new بگیرید: gh release view <tag> --repo <owner>/<repo> --json body,name.
  3. در ۲ تا ۵ نقطه، اثر روی کاربر را خلاصه کنید؛ یادداشت بالادستی را کپی نکنید و لینک بدهید.
  4. اگر ورودی سطح PR قبلاً Bump را ذکر کرده، همان را غنی کنید، تکرار نکنید.
  5. اگر ۳ Bump یا بیشتر ذکری ندارند، بخش ## Platform Components بیفزایید.

اگر یادداشت بالادستی نیافتید، فقط نسخه را بدون نقطه بنویسید. نقاط ساختگی بدتر از جزئیات ناقص‌اند.

۱۳. Nightly

Nightly یک کپی قابل‌نصب از main روی رجیستری عمومی/اصلی است؛ نه Build دوباره و نه Release. build-main.yaml هر Push به main را در رجیستری CI می‌سازد؛ Nightly آن Build را به رجیستری Release (مثلاً registry.infynex.ir) ارتقا می‌دهد و نصب‌پذیری‌اش را اثبات می‌کند. nightly.yaml روزانه (با متغیر مخزن NIGHTLY_ENABLED) در سه مرحله اجرا می‌شود:

  1. mirror: Commit دقیقی که packages:main از آن ساخته شده پیدا می‌شود و hack/nightly-mirror.sh تمام Imageهای اجزای پلتفرم را با Digest (بیت به بیت) کپی می‌کند. آرتیفکت بازنویسی‌شدهٔ packages و Chart نصب‌کننده با Tag زیر منتشر می‌شوند: 0.0.0-nightly.<YYYYMMDD>.<run-id>.<attempt> و یک Tag شناور nightly.
  2. build-disk: دیسک Talos ‏nocloud از Imager بالادستی ساخته و به‌عنوان آرتیفکت OCI منتشر می‌شود تا Nightly روی سخت‌افزار واقعی هم نصب‌پذیر باشد.
  3. e2e: مجموعهٔ کامل اپلیکیشن‌ها اجرا می‌شود (Nightly Diff ندارد، پس Test Impact Analysis اعمال نمی‌شود).

برای Nightly هیچ GitHub Release و Tag ماژول Go ساخته نمی‌شود. retention.yaml نسخه‌های قدیمی Nightly را پاک می‌کند: ۱۴ نسخهٔ جدید هر بسته، با حفظ Tag شناور nightly، Tagهای Release و نسخه‌های بدون Tag. Candidateهای رهاشدهٔ Promote هم فقط اگر همهٔ شرایط زیر برقرار باشد حذف می‌شوند: همهٔ Tagهایشان با الگوی سخت‌گیرانهٔ promotion-… بخواند (Retag‌شده به Stable حذف نمی‌شود)، بیش از ۳۰ روز از آخرین به‌روزرسانی گذشته باشد، Digest آن در PR باز Release (از همین مخزن، با نویسندهٔ CI Bot و برچسب release) ثابت نشده باشد و در main یا هر release-X.Y هم Pin نباشد.

نصب یک Nightly:

helm upgrade --install platform \
  oci://registry.infynex.ir/infynex-io/infynex/installer \
  --version 0.0.0-nightly.20261107.12345678.1
# یا برای آخرین Nightly: --version nightly

مسیر Chart و نام آن را مطابق نام واقعی Chart نصب‌کنندهٔ خودتان در مخزن جایگزین کنید.

۱۴. آتش‌هایی که قبلاً در زمان انتشار دیده شده‌اند

این‌ها رگرسیون‌های واقعی‌اند که به کاربران رسیدند. الگو در همه یکی است: کد از CI گذشت، در نسخهٔ پایدار منتشر شد و برای مشتری خراب شد. آن‌ها را مثل فهرست خلأهای تأیید پیش از انتشار بخوانید.

۱) رگرسیون ingressClassName در cert-manager (نسخهٔ ۱.۳.۰)

یک Commit، HTTP-01 را به حاشیه‌نویسی مدرن acme.cert-manager.io/http01-ingress-ingressclassname منتقل کرد. کد خواندن آن حاشیه‌نویسی در cert-manager بالادستی اضافه شده بود ولی به شاخهٔ release-1.19 Backport نشد، در حالی که نسخهٔ منتشرشده cert-manager v1.19.3 داشت. حاشیه‌نویسی در زمان اجرا بی‌سروصدا نادیده گرفته شد و Ingressهای Solver به کلاس پیش‌فرض ClusterIssuer برگشتند؛ تأیید Let’s Encrypt برای هر Tenant که publishing.ingressName != tenant-root داشت خراب شد.

چرا CI نگرفت: Commit خودبسنده بود و helm template تمیز رندر شد. هیچ E2E‌ای Tenantی با کلاس غیرپیش‌فرض بسازد و صدور واقعی گواهی را ببیند وجود نداشت؛ تأیید از طرف مستندات انجام شده بود و نه از روی کد منتشرشده. چه چیزی می‌گرفت: E2E با Tenant غیر ریشه و گواهی LE-staging که تأیید کند Ingress Solver روی کلاس همان Tenant می‌نشیند.

۲) شکست ارتقای kubevirt-instancetypes (نسخهٔ ۱.۴.۰)

یک دستور sed در Makefile تنها فرزند preferredTPM را حذف می‌کرد و در شش Preference ویندوزی preferredTPM: (null) باقی می‌ماند. KubeVirt نسخهٔ ۱.۶ آن را بی‌صدا می‌پذیرفت، اما Bump اپراتور به ۱.۸.۲ Schema را سخت‌گیر کرد و null برای فیلد شیء رد شد؛ helm upgrade روی هر کلاستر دارای این بسته شکست خورد.

چرا CI نگرفت: CI فقط نصب تازه را روی CRDهای جدید تست می‌کند و ارتقا از نسخهٔ قبل را اجرا نمی‌کند؛ دو تغییر دو هفته از هم فاصله داشتند. چه چیزی می‌گرفت: helm template | kubectl apply --dry-run=server روی CRDهای تازهٔ هر اپراتور ارتقایافته، یا یک مسیر E2E ارتقا از N−1.

۳) بن‌بست Readiness در Flux 2.7 ← 2.8 (نسخهٔ ۱.۴.۰)

helm-controller نسخهٔ ۱.۵ در Flux 2.8 روش پیش‌فرض Wait را به kstatus تغییر داد که هر منبع اعمال‌شده از جمله HelmReleaseهای فرزند را Poll می‌کند. بستهٔ چتر Kubernetes ۱۹ فرزند دارد که هر کدام dependsOn: parent دارند (با Guard از نوع lookup تا وابستگی فقط بعد از وجود والد فعال شود). قبل از ۲.۸ والد با بازگشت Helm آماده می‌شد؛ بعد از آن والد منتظر فرزندان و فرزندان منتظر والد: بن‌بست.

چرا CI نگرفت: چرخه فقط در Reconcile دوم دیده می‌شود (والد وجود دارد و lookup فعال می‌شود)؛ E2E نصب تازه از آن عبور می‌کند. چه چیزی می‌گرفت: E2E از نوع apply-mutate-reapply و یک الزام در قالب PR: برای هر Bump مربوط به flux/helm-controller بخش Breaking changes بالادستی را در PR بچسبانید.

الگوهای مشترک

  1. CI نصب تازه کافی نیست. هر سه رگرسیون از مسیرهای نصب تازه گذشتند.
  2. ارتقای مقادیر پیش‌فرض بالادستی شکل رایج است: سخت‌گیری CRD، تغییر روش پیش‌فرض Wait و رانش سطح API. خواندن یادداشت‌های بالادستی پیش از Merge دو مورد از سه را می‌گرفت.
  3. تنها شبکهٔ ایمنی تأیید زنده، «یک مهندس که قبلاً گاز گرفته شده» است. هیچ چک‌لیستی آن را الزامی نکرده بود؛ حالا بخش ۱۰ این کار را می‌کند.

خطاهای CI که مسئول انتشار معمولاً می‌بیند

علامتدستهتشخیصانتشار را می‌بندد؟
Draft release for vX.Y.Z not found روی هر Merge به release-*رگرسیون Workflow: گام Publish draft release در یک بازآرایی github-token خود را از دست داد؛ توکن پیش‌فرض نمی‌تواند Draftها را فهرست کندگام مربوط در pull-requests-release.yaml؛ دستی Publish کنید و رفع را ثبت کنیدخیر
Job تولید Changelog با کد ۴۰۲سهمیهٔ توکن تمام شدههر دو ورودی (rc-time و promote-time) و پشتیبان tags.yaml به آن می‌خورندخیر — continue-on-error؛ دستی بنویسید
E2E ‏kubernetes-test با CSINode does not contain driver csi.kubevirt.ioباگ ترتیب تستHelmRelease ‏kubernetes-${test_name}-csi هنوز در حال نصب است که PVC ساخته می‌شود؛ پیش از ساخت PVC منتظر اعلام Driver در csinode/<node> باشیدبله — ناپایدار و باگ‌های CSI واقعی را پنهان می‌کند
tenant-root در چرخهٔ ارتقا–Rollback بی‌پایان بعد از Patch OIDCباگ واقعی در تقاطع Readiness جدید و آهنگ Reconcileدنبال rate: Wait(n=1) would exceed context deadline در لاگ اپراتور و چند HR فرزند در InProgressبله
ImagePullBackOff برای ایمیج داشبورد با Tag ‏latestناپایداری رجیستریروی بعضی آینه‌های OCI شناخته‌شده؛ در مجموعهٔ Prepull نیستخیر — تکرار کنید
CI روی PR: خطای ۴۰۹ هنگام docker push روی :latest برای یک Image در دو PRتداخل هم‌زمان Manifest روی Tag شناور مشترکبا بازآرایی Tag حل شده: در PR از IMAGE_TAG=pr-<N>-<sha> و فقط در زمان انتشار IMAGE_TAG=<ref_name>خیر — gh run rerun --failed
قاعدهٔ سرانگشتی مشکلات توکن Workflow و سهمیهٔ هوش مصنوعی «خارج از باند»اند: Tag را منتشر کنید و رفع Workflow را در PR بعدی انجام دهید. باگ‌های ترتیب تست و طوفان Reconcile روی tenant-root واقعی‌اند و باید انتشار را متوقف کنند. اگر در دو انتشار پیاپی یک اصلاح دستی تکراری انجام دادید (مثلاً «Undraft کردن Release») یک Issue رگرسیون Workflow باز کنید؛ باگ‌های Workflow با راه‌حل دستی مدت‌ها بی‌صدا فاسد می‌شوند.

۱۵. تغییرناپذیری Tagها و محافظت مخزن

Tagهای منتشرشده Write-once هستند: هر vX.Y.Z یا RC پس از Push هرگز جابه‌جا یا حذف نمی‌شود. (Nightlyها اصلاً Tag Git نیستند؛ Tag رجیستری‌اند.) جابه‌جایی Tag کش Go Module Proxy را برای api/apps/v1alpha1/vX.Y.Z مسموم می‌کند و ابزارهای SBOM/Provenance را دچار رانش می‌کند؛ پس جریان انتشار طوری ساخته شده که این جابه‌جایی ذاتاً غیرممکن باشد.

فایلرفتار Tag/Branch
pull-requests-release.yamlپیش از ساخت هر نام پایدار آرتیفکت را تأیید می‌کند. vX.Y.Z را روی Merge commit Write-once می‌سازد (اگر نباشد بساز، اگر بدون تغییر باشد کاری نکن، اگر جابه‌جا می‌شد بلند شکست بخور). Tag ماژول Go را فقط برای Stable می‌سازد. release-X.Y فقط Fast-forward می‌شود. Retag Imageها هم Write-once است.
tags.yamlشاخهٔ مرحله‌ای release-X.Y.Z-rc.N قابل‌تغییر است (مقایسه پیش از Force؛ فقط در صورت تغییر واقعی). روی Tag پایدار بدون Draft قبلی بلافاصله شکست می‌خورد: «Tag پایدار از promote-rc.yaml می‌آید».
promote-rc.yamlهیچ Tag پایداری را تغییر نمی‌دهد. پیش از Staging، E2E سبز را تأیید می‌کند (مگر دور زدن بلند). شاخهٔ مرحله‌ای در Dispatch مجدد Force-push می‌شود، اما docs/changelogs/vX.Y.Z.md موجود حفظ می‌شود.

قوانین سطح مخزن (Tag Ruleset)

تضمین‌های بالا در Workflowها اعمال می‌شوند؛ مدیران مخزن آن‌ها را با دو Ruleset روی Tagهای v* پشتیبانی می‌کنند:

  • Immutability — Restrict updates و Restrict deletions بدون هیچ Bypass. هیچ بازیگری (حتی CI App) نمی‌تواند Tag منتشرشده را جابه‌جا یا حذف کند. در هر زمان فعال‌کردنش امن است؛ هیچ مرحله‌ای Tag را جابه‌جا یا حذف نمی‌کند.
  • Creation control — Restrict creations با CI App به‌عنوان تنها Bypass، به‌علاوهٔ Limit branches/tags updated in a single push برابر ۱. این «Tag دستی نزنید» را از رسم به اجبار تبدیل می‌کند. فقط بعد از شروع استفاده از cut-prerelease.yaml فعالش کنید، وگرنه مهندسان نمی‌توانند RC بسازند. (Tag ماژول Go یعنی api/apps/v1alpha1/* هم یک جفت Ruleset معادل دارد.)
قدم‌به‌قدم فعال‌سازی ۱) ابتدا Immutability را فعال کنید. ۲) یک بار Cut Pre-release Tag را روی یک Tag آزمایشی امتحان کنید. ۳) سپس Creation control را فعال کنید.

۱۶. جدا کردن PR بستهٔ مسدودکنندهٔ انتشار و پاکسازی

تقسیم PR بسته‌ای (Bundle)

گاهی کاری که قبل از انتشار باید Merge شود یک بستهٔ ۴۰ Commitی است (پایدارسازی CI، Bump وابستگی‌ها، اصلاح Chart). تقسیم آن را قابل‌بازبینی و قابل‌بقا می‌کند. راهبرد کارآمد:

  1. ببینید چه چیزی از قبل روی main است. با git merge-base --is-ancestor و جست‌وجوی موضوع Commitهای تکراری را کنار بگذارید.
  2. PRهای باز موجود را بیابید که همان Commitها را پوشش می‌دهند؛ به‌جای PR تکراری به همان شاخه Force-push کنید تا رشتهٔ بازبینی حفظ شود.
  3. PR «گلوگاه» را شناسایی کنید: آنکه پیکربندی‌های اشتباهِ بی‌صدا را به خطای سخت تبدیل می‌کند (معمولاً Bump یک اپراتور). بقیه موازی پخش می‌شوند.
  4. اصلاح‌های پشت‌سرهمِ یک نویسنده را Squash کنید داخل هر تکه، با حفظ نویسنده از --author.
  5. PR کلیدی را Draft کنید و در بدنه به PRهای وابسته لینک بدهید؛ بازبین‌ها می‌دانند تا نشستن زنجیره عمداً CI را رد می‌کند.
  6. مراقب Pushهای هم‌زمان به رجیستری باشید. اجرای هم‌زمان ۵ PR و بیشتر می‌تواند روی Tagهای شناور مشترک تداخل کند؛ اگر Conflicted with another upload of the same manifest دیدید gh run rerun --failed بزنید.

پاکسازی پس از انتشار

  • شاخهٔ release-X.Y.Z با Merge حذف می‌شود. شاخهٔ release-X.Y را cut-prerelease.yaml در Freeze می‌سازد (فقط ساخت، هرگز Force) و Finalize آن را Fast-forward می‌کند.
  • Draft Release توسط همان Workflow منتشر می‌شود.
  • بدنهٔ Release از docs/changelogs/vX.Y.Z.md در زمان انتشار تنظیم می‌شود؛ بعد از آن update-releasenotes.yaml هر ویرایش بعدی را در Pushهای بعدی به main همگام می‌کند.
  • محلی: Worktree خود را پاک کنید (git worktree remove) و شاخه‌های Release ادغام‌شده را Prune کنید.

برگهٔ تقلب دستورها

# ساخت rc.1 و Freeze
gh workflow run cut-prerelease.yaml --ref main -f tag=v1.7.0-rc.1
# ساخت rc بعدی از شاخهٔ نگهداری
gh workflow run cut-prerelease.yaml --ref release-1.7 -f tag=v1.7.0-rc.2
# مدرک E2E دستی برای یک Tag
gh workflow run e2e-tag.yaml -f tag=v1.7.0-rc.2
# Changelog زودهنگام (اختیاری)
gh workflow run changelog-rc.yaml -f tag=v1.7.0-rc.2
# Promote
gh workflow run promote-rc.yaml -f tag=v1.7.0-rc.2
# مشاهدهٔ اجراها
gh run list --workflow tags.yaml --limit 5
gh run watch

نام ورودی‌ها (tag و …) را از بخش workflow_dispatch.inputs هر فایل تأیید کنید؛ ممکن است در مخزن شما فرق داشته باشد.

اشتباهات رایج

اشتباهدرست
Push دستی Tag v1.7.0فقط از مسیر Promote و Finalize.
ساخت rc.2 از mainDispatch از release-1.7؛ در غیر این‌صورت رد می‌شود.
Squash کردن Promote PRMerge commit واقعی، چون Tag باید روی آن بنشیند.
Merge مستندات قبل از انتشار Releaseتا انتشار پارک کنید.
ویرایش packages/ روی Promote PRPromote را دوباره Dispatch کنید.
Backport kind/feature در Patchفقط رفع باگ و امنیت.
بالا بردن targetVersion در Backportقاعدهٔ شمارنده در بخش ۱۱.
جابه‌جا کردن Tag برای رفع اشتباهنسخهٔ Patch بعدی.

۱۷. تطبیق با زیرساخت Infynex

فرایند بالا از Cozystack گرفته شده و به قابلیت‌های سکوی GitHub تکیه دارد. هنگام اجرا روی git.infynex.ir این موارد را تطبیق دهید یا معادل بیابید:

در فرایند اصلیبرای Infynex بررسی کنید
cozystack-ci[bot] (GitHub App)یک حساب/توکن CI با نام مثلاً infynex-ci؛ در شرط Finalize («نویسندهٔ PR باید CI Bot باشد») نام را عوض کنید.
GHCRرجیستری داخلی (registry.infynex.ir)؛ در Workflowها، hack/*.sh و Valuesها جایگزین شود.
Tag Rulesetsمعادل Protected Tags در سرور شما.
gh CLI و APIاگر سرور API سازگار ندارد، اسکریپت‌های Changelog و Triage را با API آن بازنویسی کنید.
Copilot (COPILOT_GITHUB_TOKEN)اگر در دسترس نیست، تولید Changelog را به‌صورت دستی (طبق docs/agents/changelog.md) انجام دهید؛ Job عمداً غیرمسدودکننده است.
مخزن وب‌سایت و FETCH_REFاگر مخزن مستندات جداگانه ندارید، Job ‏website-docs را غیرفعال کنید.
مسیر ماژول Gogit.infynex.ir/infynex-io/infynex/api/apps/v1alpha1 در go.mod و Importها.
مرجع‌های درون سند مثل docs/agents/*.mdاین فایل‌ها را همراه مخزن منتقل کنید یا مسیرشان را اصلاح کنید.
توصیهٔ اجرایی برای اولین انتشار داخلی، Alpha و Beta را اجباری نکنید. ابتدا v1.7.0-rc.1 را از main بسازید، release-1.7 منجمد می‌شود و از آن به بعد همهٔ اصلاحات نسخهٔ ۱.۷ با Cherry-pick و RCهای پیاپی مدیریت می‌شوند. پیش از آن یک Dry-run کامل روی یک Tag آزمایشی بزنید و Secrets، دسترسی رجیستری و Branch protection را بیازمایید.

۱۸. مراجع

نام فایل‌های workflow و اسکریپت‌ها همان‌هایی است که در مخزن بالادستی هستند؛ اگر در مخزن Infynex تغییر نام داده‌اید، این سند را هم به‌روز کنید.