راهنمای کامل انتشار نسخه در پلتفرم Infynex
از اولین Release Candidate تا نسخهٔ پایدار، نسخهٔ وصله (Patch)، Backport و Changelog؛ همراه با مثال، دیاگرام و چکلیست.
۱. این راهنما برای چیست؟
این سند هم طراحی فرایند (مدل انتشار چگونه کار میکند) و هم دفترچهٔ عملیاتی (دقیقاً چه کاری انجام بدهیم، چه چیزی ممکن است خراب شود و چه چیزی را باید بررسی کنیم) برای انتشار نسخههای پلتفرم 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)
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-once | Tag پس از ساخته شدن هرگز جابهجا یا حذف نمیشود. |
| CI App | هویت خودکار (Bot/Token) که Tagها و PRهای انتشار را میسازد؛ در این سند با نام نمونهٔ infynex-ci[bot] آمده است. |
۲. نمای کلی مدل انتشار
سه نوع انتشار داریم:
| نوع | مثال | توضیح |
|---|---|---|
| RC | v1.7.0-rc.1 | نسخهٔ پیشانتشار برای تست و اعتبارسنجی نهایی. اولین RC از main ساخته میشود. |
| Regular | v1.7.0 | نسخهٔ پایدار؛ همیشه از ارتقای یک RC حاصل میشود، نه Build دوباره و نه Tag دستی. |
| Patch | v1.7.1 | فقط رفع باگ، پس از انتشار نسخهٔ پایدار، بر پایهٔ شاخهٔ release-1.7. |
همهٔ این Tagها باید با عبارت منظم زیر تطبیق داشته باشند؛ Push شدن Tag همان چیزی است که کل خط لولهٔ CI را فعال میکند:
^v\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$
چهار قاعدهٔ طلایی
- Tag پایدار را هرگز دستی نزنید.
vX.Y.Zفقط توسط جریان Promote و روی Merge commit ساخته میشود. - Tag پیشانتشار را هم دستی Push نکنید. از workflow با نام
Cut Pre-release Tagاستفاده کنید (دلیلش در بخش ۸ آمده است). - اولین RC خط نسخه را منجمد میکند. از آن پس هر RC دیگر باید از
release-X.Yبیاید و Dispatch ازmainرد میشود. - همان بایتهایی که تست شدند منتشر میشوند. نسخهٔ پایدار همان Imageهای RC است؛ فقط با Digest دوباره Tag میخورد.
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]
۳. پیشنیازهای راهاندازی در git.infynex.ir
فقط داشتن سورس و فایلهای workflow کافی نیست. پیش از اولین انتشار باید زیرساخت اطراف آن هم روی سرور داخلی آماده باشد.
| مورد | چرا لازم است | مثال برای Infynex |
|---|---|---|
| CI App / Bot | Tag پیشانتشار، 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 و محیط E2E | rc-e2e مجموعهٔ کامل نصب را روی دیسک و Imageهای منتشرشده اجرا میکند | Runner داخلی با دسترسی به رجیستری |
| مخزن مستندات (وبسایت) | Promote یک PR برای «مرجع اپلیکیشنها» در مخزن مستندات باز میکند | infynex-io/website (اگر ندارید، این Job غیرمسدودکننده است و ⚠️ میگذارد) |
| مسیر ماژول Go | Tag پایدار api/apps/v1alpha1/vX.Y.Z هم ساخته میشود؛ مسیر ماژول در go.mod باید با آدرس جدید هماهنگ باشد | git.infynex.ir/infynex-io/infynex/api/apps/v1alpha1 |
gh، Copilot برای Changelog) تکیه دارد. اگر git.infynex.ir سکوی دیگری است، برای هر مورد معادل بیابید (بخش ۱۷). قبل از اولین انتشار واقعی، کل مسیر را روی یک Tag آزمایشی تمرین کنید..github/workflows/ مخزناند. فهرستشان در بخش ۸ است. اگر سرور شما پوشهٔ دیگری میخواند (مثلاً .gitea/workflows/) مسیرها را متناسب جابهجا کنید.۴. مدل شاخهها و Tagها
برای اولین انتشار v1.7.0 با یک شاخه شروع میکنید: main. هنوز هیچ Release داخلی منتشر نشده است.
infynex-io/infynex
└── main ← تنها شاخه در ابتدا
در طول عمر نسخه، این شاخهها و Tagها ساخته میشوند:
| نوع | نام | چه کسی میسازد / چه زمانی | ماندگاری |
|---|---|---|---|
| Branch | main | از ابتدا | دائمی |
| Tag | v1.7.0-rc.1 | workflow Cut Pre-release Tag از main | دائمی، Write-once |
| Branch | release-1.7 | همزمان با rc.1 (Freeze) | دائمی (شاخهٔ نگهداری) |
| Branch | release-1.7.0-rc.1 | CI پس از Build هر RC (Digestها را ثابت میکند) | موقت/قابل بازنویسی |
| Tag | v1.7.0-rc.2 و … | Cut Pre-release Tag از release-1.7 | دائمی |
| Branch | release-1.7.0 | promote-rc.yaml هنگام Promote | موقت؛ پس از Merge حذف میشود |
| Tag | v1.7.0 | Finalize، روی Merge commit | دائمی، Write-once |
| Tag | api/apps/v1alpha1/v1.7.0 | Finalize، روی همان Commit | دائمی |
| Tag | v1.7.1-rc.1 | Cut Pre-release Tag از release-1.7 | دائمی |
| Branch | release-1.7.1 | Promote برای Patch | موقت |
| Tag | v1.7.1 | Finalize | دائمی |
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
- انتشار rc.1 از main و Freeze
در مخزن، بخش Actions ← workflow Cut Pre-release Tag← Run workflow با شاخهٔmainو تگv1.7.0-rc.1.
نتیجه: Tag روی HEAD شاخهٔ main بهعنوان CI App ساخته میشود، شاخهٔ# معادل خط فرمان (نام ورودی را از فایل cut-prerelease.yaml چک کنید) gh workflow run cut-prerelease.yaml --ref main -f tag=v1.7.0-rc.1release-1.7در همان Commit ساخته میشود (Freeze)، و سپسtags.yamlاجرا میشود: Imageهای RC را Build میکند، پیشانتشار را منتشر میکند، شاخهٔrelease-1.7.0-rc.1را با Digestهای ثابت Push میکند و Job اجباریِrc-e2eرا اجرا میکند. - پایش 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 را میسازد. - Promote: اجرای promote-rc.yaml
Actions ←promote-rc.yaml← Run workflow با تگ RC سالم (مثلاًv1.7.0-rc.2).
Job gh workflow run promote-rc.yaml -f tag=v1.7.0-rc.2parseپیش از هر کاری E2E را تأیید میکند: یاrc-e2eموفق در آخرین اجرایtags.yamlهمان تگ، یا یک اجرای موفق دستیe2e-tag.yaml. سپس:- نسخهٔ RC را در Tagهای Image به
1.7.0بازنویسی میکند (Digestها همان RC میمانند)، آن را Commit میکند، پوشهٔpackages/را زیر یک Tag موقتpromotion-*منتشر میکند و Commit دوم را که Digest آن Candidate را در Installer ثابت میکند میسازد و شاخهٔrelease-1.7.0را Push میکند. - پیشنویس (Draft) Release
v1.7.0را با Assetهای بازنویسیشده میسازد. - Changelog را (موازی) تولید میکند — یا اگر دکمهٔ rc-time را زدهاید، همان فایل را کپی میکند — و روی شاخهٔ
release-1.7.0Commit میکند. این بخش غیرمسدودکننده است. - یک PR در مخزن مستندات (
update-docs-v1.7.0) باز میکند. غیرمسدودکننده و تا انتشار Release نباید Merge شود. - 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. - نسخهٔ RC را در Tagهای Image به
- بازبینی و Merge شدن Promote PR
PR را طبق چکلیست فاز ۴ بررسی کنید: خط «✅ RC full e2e was verified»، سبز بودنVerify release packages candidate، وجود Changelog و خط مستندات. سپس Merge کنید — Squash نکنید؛ Tag پایدار باید روی یک Merge commit واقعی بنشیند. شاخهٔrelease-1.7.0با Merge حذف میشود. - Finalize (خودکار پس از Merge)
workflow pull-requests-release.yamlفعال میشود و این کارها را به ترتیب انجام میدهد:- آرتیفکت packages را با Digest میکشد، با درخت Mergeشده تطبیق میدهد، مطمئن میشود هیچ Tag مربوط به rc باقی نمانده و مجموعهٔ Digestهای کانتینر برابر RC اصلی است.
- Tag
v1.7.0را روی Merge commit میسازد (Write-once). - Tag ماژول Go یعنی
api/apps/v1alpha1/v1.7.0را روی همان Commit میسازد. - مطمئن میشود
release-1.7در همان Commit هست (معمولاً بدون عمل، چون Freeze ساخته است). - Release را از Draft به منتشرشده (
latest) تبدیل میکند و بدنهٔ آن را ازdocs/changelogs/v1.7.0.mdمیگیرد. - آرتیفکت packages و Imageهای RC را با Digest به
v1.7.0Retag میکند (بدون Build) و Chart نصبکننده را منتشر میکند؛:latestفقط اگر این نسخه جدیدترین نسخهٔ پایدار باشد.
- مستندات و اطلاعرسانی
پس از انتشار 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"
ب) 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"
ج) پس از 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"
جزئیات 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 خودش را دارد.
main ساخته شود هر چیزی که بعد از rc.1 Merge شده بیصدا وارد نسخه میشود و نسخهٔ منتشرشده میتواند کدی داشته باشد که هیچ RCای آن را تأیید نکرده است. Freeze دقیقاً برای جلوگیری از همین است.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) برای قابلیتهای موجود در ۱.۷.۰ داریم.
- Cherry-pick اصلاحات به release-1.7
فقط اصلاحاتِ قابلیتهایی که درv1.7.0بودهاند. میتوان بلافاصله بعد از Merge در main یا درست قبل از انتشار انجام داد. برچسبbackportاین کار را خودکار میکند (بخش ۱۱). - ساخت RC وصله
Cut Pre-release Tagاز شاخهٔrelease-1.7با تگv1.7.1-rc.1. CI میسازد،release-1.7.1-rc.1را Push میکند وrc-e2eاجباری را اجرا میکند. - پایش rc-e2e + چکلیست (و در صورت تمایل
Generate RC Changelog، کهdocs/changelogs/v1.7.1.mdرا میسازد). - Promote
promote-rc.yamlبا تگv1.7.1-rc.1— همان پنج Job. PR مستندات برای Patch پوشهٔ موجودv1.7/را درجا بهروز میکند (چون Patch پوشهٔnext/را ارتقا نمیدهد). - بازبینی و Merge مطابق چکلیست فاز ۴؛ Squash نکنید.
- Finalize: تأیید آرتیفکت، Tag
v1.7.1، Tag ماژول Go، انتشار Release (فقط اگر جدیدترین نسخهٔ پایدار باشدlatestمیشود)، Retag با Digest و Chart. - 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"
v1.7.1 باگ بحرانی پیدا شد، Tag را جابهجا نکنید؛ v1.7.2 را از همین مسیر منتشر کنید.۷. Alpha و Beta و RC
| نوع | مثال | کاربرد | Promote میشود؟ | E2E |
|---|---|---|---|---|
| Alpha | v1.7.0-alpha.1 | تست اولیهٔ قابلیتها و بازخورد | خیر | فقط با دکمهٔ دستی e2e-tag.yaml |
| Beta | v1.7.0-beta.1 | نسخهٔ نسبتاً کامل برای بازخورد | خیر | فقط با دکمهٔ دستی |
| RC | v1.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.yaml | Push شدن 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.yaml | PR | Candidate را با درخت Merge احتمالی و آرتیفکت اصلی RC تطبیق میدهد تا Status اجباری E2E Tests سبز شود. |
pull-requests-release.yaml | Merge شدن Promote PR | Finalize: تأیید دوباره، Tag پایدار و ماژول Go، انتشار Release با Changelog، Retag با Digest و Chart. |
update-releasenotes.yaml | Push به main که docs/changelogs/v*.md را تغییر دهد | همگامسازی بدنهٔ Release با Changelog (برای ویرایشهای بعدی و همگامسازی دستی). |
changelog-rc.yaml | دستی، اختیاری | Changelog را در زمان RC میسازد و روی شاخهٔ مرحلهای ذخیره میکند. |
changelog-generate.yaml | فراخوانی از دو مورد بالا | هستهٔ مشترک تولید Changelog. |
backport.yaml | PR Mergeشده روی main با برچسب | ساخت خودکار PR برای Cherry-pick (بخش ۱۱). |
nightly.yaml، retention.yaml | زمانبندی | Nightly و پاکسازی (بخش ۱۳). |
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)
۹. فازهای خط لولهٔ انتشار
فاز ۱ — 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.yamlpackages/system/*/values.yamlpackages/apps/kubernetes/images/*.tagpackages/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) باشد.
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» دارد.
راههای بازیابی به ترتیب ترجیح:
- فایل
docs/changelogs/vX.Y.Z.mdرا دستی روی شاخهٔrelease-X.Y.ZCommit کنید. (Promote دوباره آن را حفظ میکند.) promote-rc.yamlرا دوباره Dispatch کنید؛ هر چیزی که از قبل روی شاخه باشد برنده است.- بدون آن Merge کنید و بگذارید
tags.yaml::generate-changelog(پشتیبان) بعداً یک PR Changelog رویmainباز کند.
شکستهای شناختهشده: اتمام سهمیهٔ توکن هوش مصنوعی (خطای 402)، Timeout سیدقیقهای، و خروجی ناقص که hack/validate-changelog.sh آن را میگیرد (باید لینک Release، لینک Compare به همین نسخه و حداقل یک بخش ## داشته باشد؛ Patch کوتاه هم مجاز است).
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ها میخواند.
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را پایین نمیآورد.:latestImageها و 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 شکست میخورد.
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 میرود و هرگز شرط را برآورده نمیکند.
وقتی ربات شکست میخورد
- اجرای ناتمام قبلی را پیدا و پاک کنید، چون اجرای دوباره پشت آن صف میماند. روی وضعیت فیلتر نکنید (اجرایی که منتظر Runner است
in_progressنیست). تأیید کنید اجرا مال همان PR است.gh run list --workflow backport.yaml --branch <head-branch-of-the-PR> - برچسب Backport را بردارید و دوباره اضافه کنید؛ رویداد Label تنها Trigger دیگر است.
- 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
| شامل شود ✅ | شامل نشود ❌ |
|---|---|
|
|
موارد مرزی: بازآرایی بزرگ با 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
کدی که شمارندهٔ یکنواخت (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.7Backport میکنید و سادهلوحانهtargetVersionرا ۵ ← ۶ میبرید. - مشتری ۱.۷.x اجرا میکند و
cozystack-version=6را در کلاستر ثبت میکند (نام این کلید در کد فعلی است). - هنگام ارتقا به نسخهٔ بعدی (
targetVersion=10) اجراکنندهseq 6 9را میرود و Migration شمارهٔ ۵ خودِ main هرگز اجرا نمیشود؛ آن شماره را Backport اشغال کرده است.
راهحلها به ترتیب ترجیح:
targetVersionرا در Backport بالا نبرید. فقط فایل Migration را بفرستید و Guard سمت Chart را در شاخهٔ نگهداری شل کنید تا مقدار جدید بدون اجرای Migration کار کند.- اگر ناچار به بالا بردن هستید، نسخهٔ Idempotent Migration ردشدهٔ main را در یک شمارهٔ بالاتر تکرار کنید.
- قبل از تصمیم، وضعیت شمارنده را بین شاخهها ممیزی کنید:
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"
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 ارتقایافته:
- فیلد
sources:(معمولاً یک آدرس Git) را بخوانید. - یادداشتهای انتشار را فقط در بازهٔ
old < x <= newبگیرید:gh release view <tag> --repo <owner>/<repo> --json body,name. - در ۲ تا ۵ نقطه، اثر روی کاربر را خلاصه کنید؛ یادداشت بالادستی را کپی نکنید و لینک بدهید.
- اگر ورودی سطح PR قبلاً Bump را ذکر کرده، همان را غنی کنید، تکرار نکنید.
- اگر ۳ Bump یا بیشتر ذکری ندارند، بخش
## Platform Componentsبیفزایید.
اگر یادداشت بالادستی نیافتید، فقط نسخه را بدون نقطه بنویسید. نقاط ساختگی بدتر از جزئیات ناقصاند.
۱۳. Nightly
Nightly یک کپی قابلنصب از main روی رجیستری عمومی/اصلی است؛ نه Build دوباره و نه Release. build-main.yaml هر Push به main را در رجیستری CI میسازد؛ Nightly آن Build را به رجیستری Release (مثلاً registry.infynex.ir) ارتقا میدهد و نصبپذیریاش را اثبات میکند. nightly.yaml روزانه (با متغیر مخزن NIGHTLY_ENABLED) در سه مرحله اجرا میشود:
- mirror: Commit دقیقی که packages:main از آن ساخته شده پیدا میشود و
hack/nightly-mirror.shتمام Imageهای اجزای پلتفرم را با Digest (بیت به بیت) کپی میکند. آرتیفکت بازنویسیشدهٔ packages و Chart نصبکننده با Tag زیر منتشر میشوند:0.0.0-nightly.<YYYYMMDD>.<run-id>.<attempt>و یک Tag شناورnightly. - build-disk: دیسک Talos
nocloudاز Imager بالادستی ساخته و بهعنوان آرتیفکت OCI منتشر میشود تا Nightly روی سختافزار واقعی هم نصبپذیر باشد. - 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 بچسبانید.
الگوهای مشترک
- CI نصب تازه کافی نیست. هر سه رگرسیون از مسیرهای نصب تازه گذشتند.
- ارتقای مقادیر پیشفرض بالادستی شکل رایج است: سختگیری CRD، تغییر روش پیشفرض Wait و رانش سطح API. خواندن یادداشتهای بالادستی پیش از Merge دو مورد از سه را میگرفت.
- تنها شبکهٔ ایمنی تأیید زنده، «یک مهندس که قبلاً گاز گرفته شده» است. هیچ چکلیستی آن را الزامی نکرده بود؛ حالا بخش ۱۰ این کار را میکند.
خطاهای 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 |
۱۵. تغییرناپذیری 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 معادل دارد.)
Cut Pre-release Tag را روی یک Tag آزمایشی امتحان کنید. ۳) سپس Creation control را فعال کنید.۱۶. جدا کردن PR بستهٔ مسدودکنندهٔ انتشار و پاکسازی
تقسیم PR بستهای (Bundle)
گاهی کاری که قبل از انتشار باید Merge شود یک بستهٔ ۴۰ Commitی است (پایدارسازی CI، Bump وابستگیها، اصلاح Chart). تقسیم آن را قابلبازبینی و قابلبقا میکند. راهبرد کارآمد:
- ببینید چه چیزی از قبل روی main است. با
git merge-base --is-ancestorو جستوجوی موضوع Commitهای تکراری را کنار بگذارید. - PRهای باز موجود را بیابید که همان Commitها را پوشش میدهند؛ بهجای PR تکراری به همان شاخه Force-push کنید تا رشتهٔ بازبینی حفظ شود.
- PR «گلوگاه» را شناسایی کنید: آنکه پیکربندیهای اشتباهِ بیصدا را به خطای سخت تبدیل میکند (معمولاً Bump یک اپراتور). بقیه موازی پخش میشوند.
- اصلاحهای پشتسرهمِ یک نویسنده را Squash کنید داخل هر تکه، با حفظ نویسنده از
--author. - PR کلیدی را Draft کنید و در بدنه به PRهای وابسته لینک بدهید؛ بازبینها میدانند تا نشستن زنجیره عمداً CI را رد میکند.
- مراقب 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 از main | Dispatch از release-1.7؛ در غیر اینصورت رد میشود. |
| Squash کردن Promote PR | Merge commit واقعی، چون Tag باید روی آن بنشیند. |
| Merge مستندات قبل از انتشار Release | تا انتشار پارک کنید. |
ویرایش packages/ روی Promote PR | Promote را دوباره 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 را غیرفعال کنید. |
| مسیر ماژول Go | git.infynex.ir/infynex-io/infynex/api/apps/v1alpha1 در go.mod و Importها. |
مرجعهای درون سند مثل docs/agents/*.md | این فایلها را همراه مخزن منتقل کنید یا مسیرشان را اصلاح کنید. |
v1.7.0-rc.1 را از main بسازید، release-1.7 منجمد میشود و از آن به بعد همهٔ اصلاحات نسخهٔ ۱.۷ با Cherry-pick و RCهای پیاپی مدیریت میشوند. پیش از آن یک Dry-run کامل روی یک Tag آزمایشی بزنید و Secrets، دسترسی رجیستری و Branch protection را بیازمایید.۱۸. مراجع
- مخزن پلتفرم: git.infynex.ir/infynex-io/
- منبع فرایند (پروژهٔ بالادستی): docs/release.md
- فرایند Changelog: docs/agents/changelog.md
- workflowها: .github/workflows
- فایلهای کمکی:
hack/promote-retag.sh،hack/verify-promoted-packages.sh،hack/nightly-mirror.sh،hack/validate-changelog.sh،docs/agents/image-refs.md
نام فایلهای workflow و اسکریپتها همانهایی است که در مخزن بالادستی هستند؛ اگر در مخزن Infynex تغییر نام دادهاید، این سند را هم بهروز کنید.