ci: split GitOps state into cloud-host-gitops repo, add Sealed Secrets, fix pipeline auth
Build and Deploy Platform / build-and-deploy (push) Failing after 20m1s
Build and Deploy Platform / build-and-deploy (push) Failing after 20m1s
- Workflow now pushes image tags to the separate cloud-host-gitops repo
(no more CI loop risk) and authenticates via CI_TOKEN secret
- Fix undefined ${REGISTRY} in Kaniko jobs, add concurrency group,
targeted tag update, and mounted kaniko-harbor-auth docker config
- Argo CD Application is now multi-source (chart from cloud-host,
values from cloud-host-gitops)
- Remove plaintext runner token and proxy credentials from manifests;
secrets are now SealedSecrets in the gitops repo
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,15 +3,26 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "gitops/platform/values-abrban.yaml"
|
|
||||||
- "**.md"
|
- "**.md"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Serialize builds so parallel pushes don't race on the GitOps values update.
|
||||||
|
concurrency:
|
||||||
|
group: build-deploy-platform
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: harbor-registry.cloudhost.svc.cluster.local:5000
|
# PULL_REGISTRY: kubelet pulls via k3s mirror → harbor-core (matches registry-pull-secret)
|
||||||
|
PULL_REGISTRY: registry.abrban.com
|
||||||
|
# PUSH_REGISTRY: kaniko pushes directly to harbor-registry (internal, no TLS)
|
||||||
|
PUSH_REGISTRY: harbor-registry.cloudhost.svc.cluster.local:5000
|
||||||
PROJECT: abrban
|
PROJECT: abrban
|
||||||
BUILD_NS: cloudhost-builds
|
BUILD_NS: cloudhost-builds
|
||||||
GITEA_URL: http://gitea-http.gitea.svc.cluster.local:3000
|
GITEA_HOST: gitea-http.gitea.svc.cluster.local:3000
|
||||||
|
# PAT of the "ci" user, stored as repo secret CI_TOKEN (names starting with GITEA_ are reserved)
|
||||||
|
GITEA_TOKEN: ${{ secrets.CI_TOKEN }}
|
||||||
|
REPO_PATH: abrban/cloud-host.git
|
||||||
|
GITOPS_REPO_PATH: abrban/cloud-host-gitops.git
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
@@ -20,7 +31,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
shell: sh
|
shell: sh
|
||||||
run: |
|
run: |
|
||||||
git clone --depth=1 --branch main "${GITEA_URL}/abrban/cloud-host.git" workspace
|
git clone --depth=1 --branch main "http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}" workspace
|
||||||
cd workspace
|
cd workspace
|
||||||
echo "Checked out $(git rev-parse --short HEAD)"
|
echo "Checked out $(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
@@ -53,29 +64,37 @@ jobs:
|
|||||||
- name: registry-pull-secret
|
- name: registry-pull-secret
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: clone
|
- name: clone
|
||||||
image: ${REGISTRY}/${PROJECT}/alpine:3
|
image: ${PULL_REGISTRY}/${PROJECT}/alpine:3
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
- apk add --no-cache git && git clone --depth=1 --branch main ${GITEA_URL}/abrban/cloud-host.git /workspace
|
- apk add --no-cache git && git clone --depth=1 --branch main http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH} /workspace
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: ws
|
- name: ws
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
containers:
|
containers:
|
||||||
- name: kaniko
|
- name: kaniko
|
||||||
image: ${REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug
|
image: ${PULL_REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug
|
||||||
args:
|
args:
|
||||||
- --dockerfile=/workspace/backend/Dockerfile
|
- --dockerfile=/workspace/backend/Dockerfile
|
||||||
- --context=dir:///workspace/backend
|
- --context=dir:///workspace/backend
|
||||||
- --destination=${REGISTRY}/${PROJECT}/cloudhost-backend:${IMAGE_TAG}
|
- --destination=${PUSH_REGISTRY}/${PROJECT}/cloudhost-backend:${IMAGE_TAG}
|
||||||
- --insecure
|
- --insecure
|
||||||
- --skip-tls-verify
|
- --skip-tls-verify
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: ws
|
- name: ws
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
- name: docker-config
|
||||||
|
mountPath: /kaniko/.docker
|
||||||
volumes:
|
volumes:
|
||||||
- name: ws
|
- name: ws
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
|
- name: docker-config
|
||||||
|
secret:
|
||||||
|
secretName: kaniko-harbor-auth
|
||||||
|
items:
|
||||||
|
- key: .dockerconfigjson
|
||||||
|
path: config.json
|
||||||
ENDJOB
|
ENDJOB
|
||||||
echo "Waiting for backend build job: ${JOB_NAME}"
|
echo "Waiting for backend build job: ${JOB_NAME}"
|
||||||
kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m
|
kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m
|
||||||
@@ -101,43 +120,59 @@ jobs:
|
|||||||
- name: registry-pull-secret
|
- name: registry-pull-secret
|
||||||
initContainers:
|
initContainers:
|
||||||
- name: clone
|
- name: clone
|
||||||
image: ${REGISTRY}/${PROJECT}/alpine:3
|
image: ${PULL_REGISTRY}/${PROJECT}/alpine:3
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
- apk add --no-cache git && git clone --depth=1 --branch main ${GITEA_URL}/abrban/cloud-host.git /workspace
|
- apk add --no-cache git && git clone --depth=1 --branch main http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH} /workspace
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: ws
|
- name: ws
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
containers:
|
containers:
|
||||||
- name: kaniko
|
- name: kaniko
|
||||||
image: ${REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug
|
image: ${PULL_REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug
|
||||||
args:
|
args:
|
||||||
- --dockerfile=/workspace/frontend/Dockerfile
|
- --dockerfile=/workspace/frontend/Dockerfile
|
||||||
- --context=dir:///workspace/frontend
|
- --context=dir:///workspace/frontend
|
||||||
- --build-arg=NEXT_PUBLIC_API_URL=https://api.abrban.com
|
- --build-arg=NEXT_PUBLIC_API_URL=https://api.abrban.com
|
||||||
- --destination=${REGISTRY}/${PROJECT}/cloudhost-frontend:${IMAGE_TAG}
|
- --destination=${PUSH_REGISTRY}/${PROJECT}/cloudhost-frontend:${IMAGE_TAG}
|
||||||
- --insecure
|
- --insecure
|
||||||
- --skip-tls-verify
|
- --skip-tls-verify
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: ws
|
- name: ws
|
||||||
mountPath: /workspace
|
mountPath: /workspace
|
||||||
|
- name: docker-config
|
||||||
|
mountPath: /kaniko/.docker
|
||||||
volumes:
|
volumes:
|
||||||
- name: ws
|
- name: ws
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
|
- name: docker-config
|
||||||
|
secret:
|
||||||
|
secretName: kaniko-harbor-auth
|
||||||
|
items:
|
||||||
|
- key: .dockerconfigjson
|
||||||
|
path: config.json
|
||||||
ENDJOB
|
ENDJOB
|
||||||
echo "Waiting for frontend build job: ${JOB_NAME}"
|
echo "Waiting for frontend build job: ${JOB_NAME}"
|
||||||
kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m
|
kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m
|
||||||
echo "Frontend build done"
|
echo "Frontend build done"
|
||||||
|
|
||||||
- name: Update GitOps values and push
|
- name: Update GitOps repo and push
|
||||||
shell: sh
|
shell: sh
|
||||||
run: |
|
run: |
|
||||||
cd workspace
|
git clone --depth=1 --branch main "http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${GITOPS_REPO_PATH}" gitops-repo
|
||||||
sed -i "s|tag: \"[^\"]*\"|tag: \"${IMAGE_TAG}\"|g" gitops/platform/values-abrban.yaml
|
cd gitops-repo
|
||||||
|
VALUES=platform/values-abrban.yaml
|
||||||
|
if command -v yq >/dev/null 2>&1; then
|
||||||
|
IMAGE_TAG="${IMAGE_TAG}" yq -i '.images.backend.tag = strenv(IMAGE_TAG) | .images.frontend.tag = strenv(IMAGE_TAG)' "${VALUES}"
|
||||||
|
else
|
||||||
|
# Only touch the tag line directly below each cloudhost-* repository line.
|
||||||
|
sed -i "/repository: .*cloudhost-backend/{n;s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|;}" "${VALUES}"
|
||||||
|
sed -i "/repository: .*cloudhost-frontend/{n;s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|;}" "${VALUES}"
|
||||||
|
fi
|
||||||
git config user.email "ci@abrban.com"
|
git config user.email "ci@abrban.com"
|
||||||
git config user.name "Gitea Actions"
|
git config user.name "Gitea Actions"
|
||||||
git add gitops/platform/values-abrban.yaml
|
git add "${VALUES}"
|
||||||
if ! git diff --cached --quiet; then
|
if ! git diff --cached --quiet; then
|
||||||
git commit -m "ci: deploy platform ${IMAGE_TAG}"
|
git commit -m "ci: deploy platform ${IMAGE_TAG}"
|
||||||
git push origin HEAD:main
|
git push origin HEAD:main
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# RUNBOOK — خط CI/CD (Gitea Actions → Kaniko → Harbor → Argo CD)
|
||||||
|
|
||||||
|
این مستند جریان کامل Build و Deploy پلتفرم را توضیح میدهد: از Push شدن کد روی `main` تا استقرار خودکار روی Kubernetes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## معماری و جریان کلی
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
Dev[Developer] -->|git push main| AppRepo["Gitea: abrban/cloud-host (کد + چارت)"]
|
||||||
|
AppRepo -->|trigger workflow| Runner["Act Runner (namespace: gitea)"]
|
||||||
|
Runner -->|"checkout با CI_TOKEN"| AppRepo
|
||||||
|
Runner -->|kubectl apply Job| Kaniko["Kaniko Job (namespace: cloudhost-builds)"]
|
||||||
|
Kaniko -->|"push با harbor_registry_user"| Harbor["Harbor (harbor-registry:5000)"]
|
||||||
|
Runner -->|"آپدیت image.tag + commit/push"| GitOpsRepo["Gitea: abrban/cloud-host-gitops (state)"]
|
||||||
|
GitOpsRepo -->|"poll (پیشفرض هر ۳ دقیقه)"| Argo["Argo CD (automated sync)"]
|
||||||
|
AppRepo -->|"Helm Chart (source دوم)"| Argo
|
||||||
|
Argo -->|"helm render + apply"| K8s["Kubernetes (namespace: cloudhost)"]
|
||||||
|
Harbor -->|"pull از طریق mirror در k3s"| K8s
|
||||||
|
Rollback["Rollback: git revert در cloud-host-gitops"] -.-> GitOpsRepo
|
||||||
|
```
|
||||||
|
|
||||||
|
مراحل به ترتیب:
|
||||||
|
|
||||||
|
1. Developer روی شاخهٔ `main` در ریپوی اپلیکیشن (`git.abrban.com/abrban/cloud-host`) push میکند.
|
||||||
|
2. Workflow در [`.gitea/workflows/build-deploy.yaml`](.gitea/workflows/build-deploy.yaml) روی Runner با لیبل `abrban-builder` اجرا میشود.
|
||||||
|
3. Runner کد را با توکن CI کلون میکند و تگ ایمیج (`YYYYMMDD-HHMM-<sha>`) را میسازد.
|
||||||
|
4. برای هر ایمیج (backend و frontend) یک Kaniko Job در namespace `cloudhost-builds` ساخته میشود که کد را کلون، ایمیج را build و به Harbor push میکند.
|
||||||
|
5. بعد از موفقیت هر دو Build، همان Runner ریپوی **`cloud-host-gitops`** را کلون میکند، مقدار `images.backend.tag` و `images.frontend.tag` را در `platform/values-abrban.yaml` عوض و commit/push میکند.
|
||||||
|
6. Argo CD (Application به نام `abrban-platform` با sync خودکار) تغییر را تشخیص میدهد و نسخهٔ جدید را در namespace `cloudhost` مستقر میکند.
|
||||||
|
|
||||||
|
> **جلوگیری از حلقهٔ CI:** کامیتِ Pipeline به ریپوی جدا (`cloud-host-gitops`) میرود که هیچ Workflowای ندارد؛ بنابراین Build دوباره trigger نمیشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ساختار Repository (دو ریپو)
|
||||||
|
|
||||||
|
### `abrban/cloud-host` — Application Repo
|
||||||
|
|
||||||
|
| مسیر | نقش |
|
||||||
|
|------|-----|
|
||||||
|
| `backend/`, `frontend/` | کد اپلیکیشن + Dockerfile |
|
||||||
|
| `backend/helm/cloudhost-platform/` | Helm Chart پلتفرم |
|
||||||
|
| `.gitea/workflows/build-deploy.yaml` | Pipeline (Build + آپدیت GitOps) |
|
||||||
|
| `gitops/` | نصب زیرساخت (Argo CD، Gitea، Sealed Secrets، k3s و…) |
|
||||||
|
|
||||||
|
### `abrban/cloud-host-gitops` — GitOps Repo (منبع حقیقت Argo CD)
|
||||||
|
|
||||||
|
| مسیر | نقش |
|
||||||
|
|------|-----|
|
||||||
|
| `platform/values-abrban.yaml` | مقادیر Production — تنها فایلی که CI آپدیت میکند |
|
||||||
|
| `argocd/application-platform.yaml` | تعریف Application (نسخهٔ mirror آن در `gitops/argocd/` ریپوی اپ هم هست) |
|
||||||
|
| `sealed-secrets/*.yaml` | SealedSecretهای CI — رمزشده و قابل کامیت |
|
||||||
|
|
||||||
|
Application در Argo CD بهصورت **multi-source** تعریف شده: چارت از `cloud-host` و values از `cloud-host-gitops`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sources:
|
||||||
|
- repoURL: https://git.abrban.com/abrban/cloud-host.git
|
||||||
|
path: backend/helm/cloudhost-platform
|
||||||
|
helm:
|
||||||
|
valueFiles:
|
||||||
|
- $values/platform/values-abrban.yaml
|
||||||
|
- repoURL: https://git.abrban.com/abrban/cloud-host-gitops.git
|
||||||
|
ref: values
|
||||||
|
```
|
||||||
|
|
||||||
|
مزیت این جداسازی: history تمیز، دسترسی نوشتن CI محدود به ریپوی state، و امکان دیدن کل تاریخچهٔ Deployها با `git log` یک ریپوی کوچک.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## احراز هویتها (چه کسی با چه چیزی به کجا وصل میشود)
|
||||||
|
|
||||||
|
| مسیر | مکانیزم | محل نگهداری |
|
||||||
|
|------|---------|--------------|
|
||||||
|
| Runner → Gitea (ثبت) | Registration Token | SealedSecret `gitea-act-runner-token` (ns `gitea`) در ریپوی gitops |
|
||||||
|
| Workflow → Gitea (clone/push هر دو ریپو) | PAT کاربر `ci` | Secret ریپوی `cloud-host` در Gitea با نام **`CI_TOKEN`** (نامهای `GITEA_*` رزرو هستند) |
|
||||||
|
| Kaniko → Harbor (push) | `harbor_registry_user` | SealedSecret `kaniko-harbor-auth` (ns `cloudhost-builds`) در ریپوی gitops |
|
||||||
|
| kubelet → Harbor (pull) | user `cloudhost` | Secret `registry-pull-secret` + mirror در `gitops/k3s/registries.yaml` |
|
||||||
|
| Argo CD → `cloud-host` (read) | repo credential | Secret `gitea-repo-creds` (ns `argocd`) |
|
||||||
|
| Argo CD → `cloud-host-gitops` (read) | PAT کاربر `ci` | SealedSecret `gitea-gitops-repo-creds` (ns `argocd`) در ریپوی gitops |
|
||||||
|
|
||||||
|
### توکن CI برای Gitea (`CI_TOKEN`)
|
||||||
|
|
||||||
|
کاربر `ci` در Gitea ساخته شده و روی هر دو ریپو دسترسی write دارد. PAT آن با scope `read:repository, write:repository` بهعنوان Secret با نام `CI_TOKEN` در **Settings → Actions → Secrets** ریپوی `cloud-host` ثبت شده است.
|
||||||
|
|
||||||
|
برای rotate: در Gitea با کاربر `ci` توکن جدید بسازید (یا از API ادمین: `POST /api/v1/users/ci/tokens`)، مقدار Secret را در تنظیمات ریپو آپدیت کنید و SealedSecret `gitea-gitops-repo-creds` را هم دوباره seal کنید.
|
||||||
|
|
||||||
|
### احراز هویت Kaniko به Harbor
|
||||||
|
|
||||||
|
Kaniko به endpoint داخلی `harbor-registry.cloudhost.svc.cluster.local:5000` push میکند که **مستقیم به کامپوننت registry** میرود و harbor-core را دور میزند. نکتهٔ مهم:
|
||||||
|
|
||||||
|
- **Robot Accountهای Harbor اینجا کار نمیکنند** — توکن آنها را harbor-core صادر میکند و endpoint داخلی به سرویس توکن دسترسی ندارد.
|
||||||
|
- credential درست، کاربر داخلی `harbor_registry_user` است با پسورد `REGISTRY_CREDENTIAL_PASSWORD` از Secret `harbor-core`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REG_PASS="$(kubectl -n cloudhost get secret harbor-core \
|
||||||
|
-o jsonpath='{.data.REGISTRY_CREDENTIAL_PASSWORD}' | base64 -d)"
|
||||||
|
kubectl -n cloudhost-builds create secret docker-registry kaniko-harbor-auth \
|
||||||
|
--docker-server=harbor-registry.cloudhost.svc.cluster.local:5000 \
|
||||||
|
--docker-username=harbor_registry_user \
|
||||||
|
--docker-password="${REG_PASS}"
|
||||||
|
```
|
||||||
|
|
||||||
|
نمونهٔ manifest: [`gitops/jobs/kaniko-harbor-auth.example.yaml`](gitops/jobs/kaniko-harbor-auth.example.yaml) — نسخهٔ واقعی بهصورت SealedSecret در ریپوی gitops است.
|
||||||
|
|
||||||
|
ورکفلو این Secret را در مسیر `/kaniko/.docker/config.json` هر دو Kaniko Job مانت میکند. چون push داخلی و بدون TLS است، فلگهای `--insecure --skip-tls-verify` لازماند — این ترافیک از کلاستر خارج نمیشود.
|
||||||
|
|
||||||
|
> **عارضهٔ جانبی push مستقیم به :5000** — Harbor DB از این ایمیجها بیخبر میماند؛ در UI هاربر دیده نمیشوند ولی pull بهدرستی کار میکند. برای دیدن تگها از registry API استفاده کنید (بخش عیبیابی).
|
||||||
|
|
||||||
|
### ارتباط Runner با Harbor
|
||||||
|
|
||||||
|
Runner خودش با Harbor حرف نمیزند؛ فقط Job میسازد. دو مسیر Harbor:
|
||||||
|
|
||||||
|
- **Push (داخلی):** `harbor-registry.cloudhost.svc.cluster.local:5000` — بدون عبور از Traefik.
|
||||||
|
- **Pull (kubelet):** `registry.abrban.com` — از طریق mirror در k3s (`scripts/apply-k3s-registries.sh`) به harbor-core route میشود.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Versioning ایمیجها
|
||||||
|
|
||||||
|
**استاندارد فعلی:** `YYYYMMDD-HHMM-<git-sha-short>` (مثلاً `20260702-1230-a1b2c3d`)
|
||||||
|
|
||||||
|
- **Immutable** است — هیچوقت یک تگ بازنویسی نمیشود (برخلاف `latest`).
|
||||||
|
- **قابل ردیابی** است — از روی تگ ایمیجِ در حال اجرا مستقیماً به کامیت میرسید.
|
||||||
|
- **مرتبشونده** است — بهترتیب زمانی دیده میشود.
|
||||||
|
|
||||||
|
از `latest` هرگز برای Deploy استفاده نکنید؛ هم قابلیت Rollback را از بین میبرد و هم Argo CD تغییری برای sync نمیبیند.
|
||||||
|
|
||||||
|
**SemVer برای Releaseها (اختیاری):** روی کامیت release یک Git Tag مثل `v1.4.0` بزنید و همان ایمیج را با `skopeo copy` تگ اضافه بزنید (rebuild لازم نیست). تگ SemVer برای انسانهاست؛ منبع حقیقتِ Deploy همان تگ SHA-دار در values است.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## آپدیت خودکار Helm Values
|
||||||
|
|
||||||
|
مرحلهٔ آخر Workflow ریپوی `cloud-host-gitops` را کلون میکند و فقط دو مقدار را در `platform/values-abrban.yaml` عوض میکند:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
images:
|
||||||
|
backend:
|
||||||
|
repository: registry.abrban.com/abrban/cloudhost-backend
|
||||||
|
tag: "20260702-1230-a1b2c3d" # ← CI این را آپدیت میکند
|
||||||
|
frontend:
|
||||||
|
repository: registry.abrban.com/abrban/cloudhost-frontend
|
||||||
|
tag: "20260702-1230-a1b2c3d" # ← CI این را آپدیت میکند
|
||||||
|
```
|
||||||
|
|
||||||
|
اگر `yq` روی Runner موجود باشد از آن استفاده میشود، وگرنه `sed` هدفمند (فقط خطِ `tag:` بلافاصله بعد از `repository: ...cloudhost-*`) اجرا میشود.
|
||||||
|
|
||||||
|
جایگزین بررسیشده و کنارگذاشتهشده: **Argo CD Image Updater** — با روش فعلی همپوشانی دارد و شفافیت کامیتِ صریح از CI را ندارد.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
|
||||||
|
چون Deploy فقط از Git انجام میشود، Rollback هم یک عملیات Git است — این بار در ریپوی `cloud-host-gitops`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.abrban.com/abrban/cloud-host-gitops.git && cd cloud-host-gitops
|
||||||
|
|
||||||
|
# 1. پیدا کردن کامیت deploy مشکلدار
|
||||||
|
git log --oneline -- platform/values-abrban.yaml
|
||||||
|
|
||||||
|
# 2. برگرداندن آن (تگ ایمیج به نسخهٔ قبلی برمیگردد)
|
||||||
|
git revert <commit-sha>
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
# 3. Argo CD بهصورت خودکار به نسخهٔ قبلی sync میکند (ایمیج قبلی هنوز در Harbor هست)
|
||||||
|
```
|
||||||
|
|
||||||
|
نکتهها:
|
||||||
|
|
||||||
|
- `git revert` (نه `reset --force`) — history حفظ میشود و مشخص است چه چیزی چرا برگشت.
|
||||||
|
- **Rollback اضطراری** (وقتی Git در دسترس نیست): `argocd app rollback abrban-platform` یا Sync به revision قبلی در UI. **هشدار:** چون `selfHeal: true` فعال است، Argo در sync بعدی دوباره به HEAD گیت برمیگردد — rollback اضطراری موقتی است و باید بلافاصله با `git revert` دائمی شود.
|
||||||
|
- اگر Deployment جدید خراب باشد (CrashLoopBackOff)، بهخاطر `RollingUpdate` نسخهٔ قبلی تا آمادهشدن نسخهٔ جدید بالا میماند.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## مدیریت Secretها (Sealed Secrets)
|
||||||
|
|
||||||
|
کنترلر **Sealed Secrets** در `kube-system` نصب است (values در [`gitops/sealed-secrets/values.yaml`](gitops/sealed-secrets/values.yaml)؛ ایمیج آن از `ghcr.io/bitnami` به پروژهٔ `abrban/` هاربر seed شده). Secretهای CI بهصورت **SealedSecret** در ریپوی `cloud-host-gitops` (پوشهٔ `sealed-secrets/`) نگهداری میشوند — رمزشده با کلید عمومی کلاستر؛ فقط کنترلرِ داخل کلاستر میتواند رمزگشایی کند، پس کامیتکردنشان امن است.
|
||||||
|
|
||||||
|
| SealedSecret | Namespace | محتوا |
|
||||||
|
|--------------|-----------|-------|
|
||||||
|
| `gitea-act-runner-token` | `gitea` | توکن ثبت Runner |
|
||||||
|
| `kaniko-harbor-auth` | `cloudhost-builds` | dockerconfig کاربر `harbor_registry_user` |
|
||||||
|
| `gitea-gitops-repo-creds` | `argocd` | repo credential ریپوی gitops (کاربر `ci`) |
|
||||||
|
|
||||||
|
### ساخت/بهروزرسانی یک SealedSecret
|
||||||
|
|
||||||
|
```bash
|
||||||
|
brew install kubeseal # فقط بار اول
|
||||||
|
|
||||||
|
kubectl -n <ns> create secret generic <name> --from-literal=key=value --dry-run=client -o json \
|
||||||
|
| kubeseal --controller-name=sealed-secrets-controller --controller-namespace=kube-system --format yaml \
|
||||||
|
> sealed-secrets/<name>.yaml
|
||||||
|
# سپس commit/push در ریپوی cloud-host-gitops و kubectl apply (یا sync توسط Argo در آینده)
|
||||||
|
```
|
||||||
|
|
||||||
|
> اگر Secret از قبل در کلاستر وجود دارد و میخواهید کنترلر آن را تصاحب کند، اول annotate کنید:
|
||||||
|
> `kubectl -n <ns> annotate secret <name> sealedsecrets.bitnami.com/managed="true"`
|
||||||
|
|
||||||
|
Secretهایی که هنوز دستیاند (خارج از چرخهٔ CI): `abrban-wildcard-tls`، `registry-pull-secret`، `registry-egress-proxy`، `harbor-core` (ساختهٔ Helm) — میتوانند بهتدریج seal شوند.
|
||||||
|
|
||||||
|
> **نکتهٔ امنیتی:** توکن ثبت Runner و پسورد پروکسی که قبلاً در history گیت افشا شده بودند rotate شدهاند (توکن Runner جدید صادر و Runner دوباره ثبت شد). پسورد کاربر پروکسی (`builder`) روی سرور پروکسی هنوز باید توسط ادمین عوض شود؛ بعد از تغییر، Secret `registry-egress-proxy` را در namespaceهای `cloudhost` و `gitea` آپدیت کنید.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practiceهای GitOps در این استک (چکلیست)
|
||||||
|
|
||||||
|
- [x] **Git تنها منبع حقیقت** — Argo CD با `automated + prune + selfHeal`؛ تغییر دستی با `kubectl edit` برگردانده میشود.
|
||||||
|
- [x] **جداسازی App Repo از GitOps Repo** — history تمیز و دسترسی حداقلی CI.
|
||||||
|
- [x] **تگ Immutable بهجای `latest`** — هر Build تگ یکتا دارد.
|
||||||
|
- [x] **جلوگیری از CI Loop** — کامیت CI به ریپوی جدا میرود که Workflow ندارد.
|
||||||
|
- [x] **Build بدون Docker Daemon** — Kaniko داخل Job، بدون `docker.sock` و بدون privileged.
|
||||||
|
- [x] **جداسازی push/pull هاربر** — push داخلی بدون عبور از Ingress؛ pull از طریق mirror k3s.
|
||||||
|
- [x] **Concurrency در Workflow** — دو push پشتسرهم روی آپدیت values با هم race نمیکنند.
|
||||||
|
- [x] **Secretهای GitOps-شده** — Sealed Secrets نصب و secretهای CI رمزشده در Git.
|
||||||
|
- [ ] **محیط Staging** — با `platform/values-staging.yaml` و Application دوم قابل اضافهشدن است.
|
||||||
|
- [ ] **Notification** — Argo CD Notifications برای اطلاع از Sync موفق/ناموفق.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## عیبیابی سریع
|
||||||
|
|
||||||
|
| علامت | بررسی |
|
||||||
|
|-------|-------|
|
||||||
|
| Workflow اجرا نمیشود | `kubectl -n gitea logs deploy/gitea-act-runner` — ثبت Runner و لیبل `abrban-builder` |
|
||||||
|
| Build fail — clone | معتبربودن Secret `CI_TOKEN` در تنظیمات ریپوی `cloud-host` |
|
||||||
|
| Build fail — push به Harbor | `kubectl -n cloudhost-builds get secret kaniko-harbor-auth`؛ پسورد باید با `REGISTRY_CREDENTIAL_PASSWORD` هاربر یکی باشد |
|
||||||
|
| کامیت values push نمیشود | دسترسی write کاربر `ci` روی `cloud-host-gitops` |
|
||||||
|
| Argo sync نمیکند | `kubectl -n argocd get app abrban-platform`؛ هر دو repo credential (`gitea-repo-creds` و `gitea-gitops-repo-creds`) |
|
||||||
|
| Pod ایمیج را pull نمیکند | `registry-pull-secret` در ns `cloudhost` و mirror k3s (`scripts/apply-k3s-registries.sh`) |
|
||||||
|
| دیدن تگهای موجود در registry | از داخل کلاستر: `wget -qO- "http://harbor_registry_user:<REG_PASS>@harbor-registry.cloudhost.svc.cluster.local:5000/v2/abrban/cloudhost-backend/tags/list"` |
|
||||||
|
| SealedSecret باز نمیشود | `kubectl get sealedsecrets -A` (ستون SYNCED) و لاگ `kubectl -n kube-system logs deploy/sealed-secrets-controller` |
|
||||||
+10
-1
@@ -52,6 +52,9 @@ for ns in argocd gitea cloudhost-builds; do
|
|||||||
kubectl -n cloudhost get secret registry-pull-secret -o yaml | sed "s/namespace: cloudhost/namespace: ${ns}/" | kubectl apply -f -
|
kubectl -n cloudhost get secret registry-pull-secret -o yaml | sed "s/namespace: cloudhost/namespace: ${ns}/" | kubectl apply -f -
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# پروکسی egress برای act-runner (دانلود kubectl در initContainer)
|
||||||
|
kubectl -n cloudhost get secret registry-egress-proxy -o yaml | sed 's/namespace: cloudhost/namespace: gitea/' | kubectl apply -f -
|
||||||
|
|
||||||
# 5. git push سورس به Gitea (نه docker push)
|
# 5. git push سورس به Gitea (نه docker push)
|
||||||
# git remote add gitea https://git.abrban.com/abrban/cloud-host.git
|
# git remote add gitea https://git.abrban.com/abrban/cloud-host.git
|
||||||
# git push gitea main
|
# git push gitea main
|
||||||
@@ -63,6 +66,8 @@ done
|
|||||||
./scripts/gitops-deploy.sh
|
./scripts/gitops-deploy.sh
|
||||||
|
|
||||||
# 8. Gitea Actions runner
|
# 8. Gitea Actions runner
|
||||||
|
# توکن ثبت: SealedSecret در ریپوی cloud-host-gitops (sealed-secrets/gitea-act-runner-token.yaml)
|
||||||
|
# یا دستی: kubectl -n gitea create secret generic gitea-act-runner-token --from-literal=token='<TOKEN>'
|
||||||
kubectl apply -f gitops/gitea/act-runner.yaml
|
kubectl apply -f gitops/gitea/act-runner.yaml
|
||||||
|
|
||||||
# 9. Argo CD Application
|
# 9. Argo CD Application
|
||||||
@@ -73,4 +78,8 @@ kubectl apply -f gitops/argocd/application-platform.yaml
|
|||||||
|
|
||||||
Gitea Actions: [.gitea/workflows/build-deploy.yaml](../.gitea/workflows/build-deploy.yaml)
|
Gitea Actions: [.gitea/workflows/build-deploy.yaml](../.gitea/workflows/build-deploy.yaml)
|
||||||
|
|
||||||
Push به `main` → Kaniko (از Harbor proxy) → push به `abrban/` → ArgoCD sync.
|
Push به `main` → Kaniko → push به `abrban/` → کامیت tag در ریپوی [cloud-host-gitops](https://git.abrban.com/abrban/cloud-host-gitops) → ArgoCD sync.
|
||||||
|
|
||||||
|
- مقادیر Production در ریپوی جدا `abrban/cloud-host-gitops` است (`platform/values-abrban.yaml`)؛ Application بهصورت multi-source تعریف شده.
|
||||||
|
- Secretهای CI بهصورت SealedSecret در همان ریپو هستند (کنترلر در `kube-system`، values در `gitops/sealed-secrets/values.yaml`).
|
||||||
|
- مستند کامل: [RUNBOOK-CICD.fa.md](../RUNBOOK-CICD.fa.md)
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
|
# Source of truth for this file: cloud-host-gitops repo (argocd/application-platform.yaml).
|
||||||
|
# Kept here as a mirror for bootstrap convenience.
|
||||||
apiVersion: argoproj.io/v1alpha1
|
apiVersion: argoproj.io/v1alpha1
|
||||||
kind: Application
|
kind: Application
|
||||||
metadata:
|
metadata:
|
||||||
name: abrban-platform
|
name: abrban-platform
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
finalizers:
|
|
||||||
- resources-finalizer.argocd.argoproj.io
|
|
||||||
spec:
|
spec:
|
||||||
project: default
|
project: default
|
||||||
source:
|
# Multi-source: chart lives in the app repo, values (GitOps state) live in cloud-host-gitops.
|
||||||
repoURL: https://git.abrban.com/abrban/cloud-host.git
|
sources:
|
||||||
targetRevision: main
|
- repoURL: https://git.abrban.com/abrban/cloud-host.git
|
||||||
path: backend/helm/cloudhost-platform
|
targetRevision: main
|
||||||
helm:
|
path: backend/helm/cloudhost-platform
|
||||||
valueFiles:
|
helm:
|
||||||
- ../../../gitops/platform/values-abrban.yaml
|
valueFiles:
|
||||||
|
- $values/platform/values-abrban.yaml
|
||||||
|
- repoURL: https://git.abrban.com/abrban/cloud-host-gitops.git
|
||||||
|
targetRevision: main
|
||||||
|
ref: values
|
||||||
destination:
|
destination:
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: cloudhost
|
namespace: cloudhost
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
# Gitea Actions runner — host mode (no docker.sock; k3s uses containerd)
|
# Gitea Actions runner — host mode (no docker.sock; k3s uses containerd)
|
||||||
# Runner creates Kaniko Jobs in cloudhost-builds for image builds.
|
# Runner creates Kaniko Jobs in cloudhost-builds for image builds.
|
||||||
apiVersion: v1
|
#
|
||||||
kind: Secret
|
# Secrets (never commit real values):
|
||||||
metadata:
|
#
|
||||||
name: gitea-act-runner-token
|
# # Registration token: managed as SealedSecret in the cloud-host-gitops repo
|
||||||
namespace: gitea
|
# # (sealed-secrets/gitea-act-runner-token.yaml). To rotate manually:
|
||||||
type: Opaque
|
# # token from Gitea UI → Site Administration → Actions → Runners, then:
|
||||||
stringData:
|
# kubectl -n gitea create secret generic gitea-act-runner-token \
|
||||||
token: "nL63VkZEyqpCNFdF3AMM9wzQLdLlATUvXSe5Tj0R"
|
# --from-literal=token='<RUNNER_REGISTRATION_TOKEN>'
|
||||||
|
#
|
||||||
|
# # Egress proxy (copied from cloudhost namespace, needed by the kubectl download):
|
||||||
|
# kubectl -n cloudhost get secret registry-egress-proxy -o yaml \
|
||||||
|
# | sed 's/namespace: cloudhost/namespace: gitea/' | kubectl apply -f -
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
@@ -64,7 +68,10 @@ spec:
|
|||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
env:
|
env:
|
||||||
- name: HTTPS_PROXY
|
- name: HTTPS_PROXY
|
||||||
value: "http://builder:BeeldiOr!12009@45.129.38.203:9911"
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: registry-egress-proxy
|
||||||
|
key: HTTPS_PROXY
|
||||||
command:
|
command:
|
||||||
- sh
|
- sh
|
||||||
- -c
|
- -c
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Example only — DO NOT commit real credentials.
|
||||||
|
# The real secret is managed as a SealedSecret in the cloud-host-gitops repo
|
||||||
|
# (sealed-secrets/kaniko-harbor-auth.yaml).
|
||||||
|
#
|
||||||
|
# Kaniko pushes directly to the internal registry endpoint
|
||||||
|
# (harbor-registry.cloudhost.svc.cluster.local:5000), which bypasses harbor-core.
|
||||||
|
# That endpoint only accepts the internal registry credential — Harbor robot
|
||||||
|
# accounts do NOT work there (their tokens are issued by harbor-core's token
|
||||||
|
# service). Use the harbor_registry_user credential from the harbor-core secret:
|
||||||
|
#
|
||||||
|
# REG_PASS="$(kubectl -n cloudhost get secret harbor-core \
|
||||||
|
# -o jsonpath='{.data.REGISTRY_CREDENTIAL_PASSWORD}' | base64 -d)"
|
||||||
|
# kubectl -n cloudhost-builds create secret docker-registry kaniko-harbor-auth \
|
||||||
|
# --docker-server=harbor-registry.cloudhost.svc.cluster.local:5000 \
|
||||||
|
# --docker-username=harbor_registry_user \
|
||||||
|
# --docker-password="${REG_PASS}"
|
||||||
|
#
|
||||||
|
# The build-deploy workflow mounts this secret at /kaniko/.docker/config.json
|
||||||
|
# inside every Kaniko Job. See RUNBOOK-CICD.fa.md for the full procedure.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: kaniko-harbor-auth
|
||||||
|
namespace: cloudhost-builds
|
||||||
|
type: kubernetes.io/dockerconfigjson
|
||||||
|
stringData:
|
||||||
|
.dockerconfigjson: |
|
||||||
|
{
|
||||||
|
"auths": {
|
||||||
|
"harbor-registry.cloudhost.svc.cluster.local:5000": {
|
||||||
|
"username": "harbor_registry_user",
|
||||||
|
"password": "<REGISTRY_CREDENTIAL_PASSWORD>",
|
||||||
|
"auth": "<base64 of username:password>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
# Production values for abrban.com — used by ArgoCD / Gitea Actions GitOps
|
|
||||||
namespace: cloudhost
|
|
||||||
createNamespace: false
|
|
||||||
|
|
||||||
global:
|
|
||||||
storageClass: local-path
|
|
||||||
|
|
||||||
images:
|
|
||||||
backend:
|
|
||||||
repository: registry.abrban.com/abrban/cloudhost-backend
|
|
||||||
tag: "1.0.0"
|
|
||||||
pullPolicy: Always
|
|
||||||
frontend:
|
|
||||||
repository: registry.abrban.com/abrban/cloudhost-frontend
|
|
||||||
tag: "1.0.0"
|
|
||||||
pullPolicy: Always
|
|
||||||
|
|
||||||
backend:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: registry-pull-secret
|
|
||||||
sourceStorage:
|
|
||||||
enabled: true
|
|
||||||
existingSecret: ceph-app-sources-credentials
|
|
||||||
env:
|
|
||||||
NODE_ENV: production
|
|
||||||
PORT: "4000"
|
|
||||||
PLATFORM_DOMAIN: apps.abrban.com
|
|
||||||
REGISTRY_URL: registry.abrban.com
|
|
||||||
REGISTRY_PULL_URL: registry.abrban.com
|
|
||||||
BUILD_NAMESPACE: cloudhost-builds
|
|
||||||
BUILD_SERVICE_ACCOUNT: kaniko-builder
|
|
||||||
UPLOAD_DIR: /app/uploads
|
|
||||||
PLATFORM_CREATE_STORAGE_CLASS: "false"
|
|
||||||
PLATFORM_STORAGE_CLASS: rook-ceph-block
|
|
||||||
PLATFORM_STORAGE_PROVISIONER: rook-ceph.rbd.csi.ceph.com
|
|
||||||
ELASTICSEARCH_HOST: elasticsearch.logging.svc.cluster.local
|
|
||||||
ELASTICSEARCH_AUTO_PORT_FORWARD: "false"
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: registry-pull-secret
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
className: traefik
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
frontend:
|
|
||||||
host: abrban.com
|
|
||||||
panel:
|
|
||||||
host: panel.abrban.com
|
|
||||||
api:
|
|
||||||
host: api.abrban.com
|
|
||||||
tls:
|
|
||||||
enabled: true
|
|
||||||
secretName: abrban-wildcard-tls
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Sealed Secrets controller — encrypts secrets so they can live in Git.
|
||||||
|
# Install:
|
||||||
|
# helm repo add sealed-secrets https://bitnami.github.io/sealed-secrets
|
||||||
|
# helm upgrade --install sealed-secrets sealed-secrets/sealed-secrets \
|
||||||
|
# -n kube-system -f gitops/sealed-secrets/values.yaml
|
||||||
|
#
|
||||||
|
# fullnameOverride matches the default the kubeseal CLI expects
|
||||||
|
# (sealed-secrets-controller in kube-system), so kubeseal works without flags.
|
||||||
|
fullnameOverride: sealed-secrets-controller
|
||||||
|
|
||||||
|
image:
|
||||||
|
# Seeded into Harbor abrban/ project from ghcr.io/bitnami (proxy-ghcr returned 401
|
||||||
|
# for kubelet pulls; see seed job pattern in gitops/jobs/seed-ci-images.yaml).
|
||||||
|
registry: registry.abrban.com
|
||||||
|
repository: abrban/sealed-secrets-controller
|
||||||
@@ -5,9 +5,17 @@ set -euo pipefail
|
|||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
NAMESPACE="${NAMESPACE:-cloudhost}"
|
NAMESPACE="${NAMESPACE:-cloudhost}"
|
||||||
RELEASE="${RELEASE:-cloudhost}"
|
RELEASE="${RELEASE:-cloudhost}"
|
||||||
VALUES="${VALUES:-${ROOT}/gitops/platform/values-abrban.yaml}"
|
# Production values now live in the cloud-host-gitops repo (platform/values-abrban.yaml).
|
||||||
|
VALUES="${VALUES:-${ROOT}/../cloud-host-gitops/platform/values-abrban.yaml}"
|
||||||
TAG="${TAG:-}"
|
TAG="${TAG:-}"
|
||||||
|
|
||||||
|
if [[ ! -f "${VALUES}" ]]; then
|
||||||
|
echo "ERROR: values file not found: ${VALUES}" >&2
|
||||||
|
echo "Clone the GitOps repo next to this one, or pass VALUES=/path/to/values-abrban.yaml:" >&2
|
||||||
|
echo " git clone https://git.abrban.com/abrban/cloud-host-gitops.git" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -z "${TAG}" ]]; then
|
if [[ -z "${TAG}" ]]; then
|
||||||
TAG="$(grep -E '^\s+tag:' "${VALUES}" | head -1 | sed 's/.*tag: *"\?\([^"]*\)"\?.*/\1/')"
|
TAG="$(grep -E '^\s+tag:' "${VALUES}" | head -1 | sed 's/.*tag: *"\?\([^"]*\)"\?.*/\1/')"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -70,9 +70,8 @@ EOF
|
|||||||
echo "==> Waiting for build job..."
|
echo "==> Waiting for build job..."
|
||||||
kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/${JOB_NAME}" --timeout=45m
|
kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/${JOB_NAME}" --timeout=45m
|
||||||
|
|
||||||
echo "==> Updating values-abrban.yaml tag to ${IMAGE_TAG}"
|
echo "==> Build complete (tag=${IMAGE_TAG})."
|
||||||
sed -i.bak "s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|g" "${ROOT}/gitops/platform/values-abrban.yaml"
|
echo "To deploy via GitOps, commit the tag in the cloud-host-gitops repo:"
|
||||||
rm -f "${ROOT}/gitops/platform/values-abrban.yaml.bak"
|
echo " platform/values-abrban.yaml → images.backend.tag / images.frontend.tag = \"${IMAGE_TAG}\""
|
||||||
|
echo "Or for a direct Helm deploy (bootstrap only):"
|
||||||
echo "==> Build complete. Deploy with:"
|
|
||||||
echo " TAG=${IMAGE_TAG} ./scripts/gitops-deploy.sh"
|
echo " TAG=${IMAGE_TAG} ./scripts/gitops-deploy.sh"
|
||||||
|
|||||||
Reference in New Issue
Block a user