From 5422cf4af086686e3fcd9aec58e4df77dbe41d2e Mon Sep 17 00:00:00 2001 From: Ashwin Kumar Sivakumar Date: Sun, 14 Jun 2026 05:52:49 +0530 Subject: [PATCH] fix(ci): deploy admin via immutable gitops release --- .forgejo/scripts/update-gitops.py | 149 ------------------------------ .forgejo/workflows/build.yaml | 79 +++++++++------- 2 files changed, 44 insertions(+), 184 deletions(-) delete mode 100644 .forgejo/scripts/update-gitops.py diff --git a/.forgejo/scripts/update-gitops.py b/.forgejo/scripts/update-gitops.py deleted file mode 100644 index 317d66e..0000000 --- a/.forgejo/scripts/update-gitops.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -""" -Update GitOps kustomization.yaml with new image SHA tags. - -Usage: - python3 update-gitops.py \ - --repo /path/to/nxtgauge-gitops \ - --service gateway \ - --sha abc123def456... - -This script: -1. Updates the newTag for the specified service to the SHA -2. Commits and pushes to the gitops repo -3. Flux detects the change and deploys -""" - -import argparse -import os -import re -import subprocess -import sys - - -def run(cmd: list[str], cwd: str = None) -> tuple[int, str, str]: - """Run a command and return (returncode, stdout, stderr).""" - result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True) - return result.returncode, result.stdout, result.stderr - - -def update_kustomization(kustomization_path: str, service: str, sha: str) -> bool: - """Update the newTag for a service in kustomization.yaml.""" - with open(kustomization_path, "r") as f: - content = f.read() - - # Pattern to find image entry for the service - # Matches: - name: registry.nxtgauge.com/nxtgauge-rust-{service} - # newTag: something - pattern = rf'(\s+-\s+name:\s+registry\.nxtgauge\.com/nxtgauge-rust-{re.escape(service)}\n\s+newTag:\s+)[^\n]+' - - replacement = rf'\g<1>{sha}' - - new_content, count = re.subn(pattern, replacement, content) - - if count == 0: - # Try without the nxtgauge-rust- prefix (for frontend, admin, etc) - pattern = rf'(\s+-\s+name:\s+registry\.nxtgauge\.com/nxtgauge-{re.escape(service)}\n\s+newTag:\s+)[^\n]+' - new_content, count = re.subn(pattern, replacement, content) - - if count == 0: - print(f"[ERROR] Could not find image entry for service: {service}") - return False - - with open(kustomization_path, "w") as f: - f.write(new_content) - - print(f"[OK] Updated {service} to SHA {sha}") - return True - - -def main(): - parser = argparse.ArgumentParser(description="Update GitOps with new image SHA") - parser.add_argument("--repo", required=True, help="Path to gitops repo") - parser.add_argument("--service", required=True, help="Service name (e.g., gateway, users, frontend-solid)") - parser.add_argument("--sha", required=True, help="Git SHA to deploy") - parser.add_argument("--message", default=None, help="Commit message") - args = parser.parse_args() - - service_image_map = { - "gateway": "nxtgauge-rust-gateway", - "users": "nxtgauge-rust-users", - "companies": "nxtgauge-rust-companies", - "jobs": "nxtgauge-rust-jobs", - "leads": "nxtgauge-rust-leads", - "job-seekers": "nxtgauge-rust-job-seekers", - "customers": "nxtgauge-rust-customers", - "payments": "nxtgauge-rust-payments", - "employees": "nxtgauge-rust-employees", - "photographers": "nxtgauge-rust-photographers", - "makeup-artists": "nxtgauge-rust-makeup-artists", - "tutors": "nxtgauge-rust-tutors", - "developers": "nxtgauge-rust-developers", - "video-editors": "nxtgauge-rust-video-editors", - "graphic-designers": "nxtgauge-rust-graphic-designers", - "social-media-managers": "nxtgauge-rust-social-media-managers", - "fitness-trainers": "nxtgauge-rust-fitness-trainers", - "catering-services": "nxtgauge-rust-catering-services", - "ugc-content-creators": "nxtgauge-rust-ugc-content-creators", - "cron": "nxtgauge-rust-cron", - "frontend-solid": "nxtgauge-frontend-solid", - "admin-solid": "nxtgauge-admin-solid", - "ai-assistant": "nxtgauge-ai-assistant", - } - - # Determine which kustomization file to update - if service_image_map.get(args.service): - image_name = service_image_map[args.service] - else: - image_name = f"nxtgauge-{args.service}" - - # Find the right kustomization file based on service - if "frontend" in args.service: - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/overlays/prod/kustomization.yaml") - if not os.path.exists(kustomization_path): - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-frontend-solid/base/kustomization.yaml") - elif "admin" in args.service: - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-admin-solid/overlays/prod/kustomization.yaml") - if not os.path.exists(kustomization_path): - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-admin-solid/base/kustomization.yaml") - elif "ai-assistant" in args.service: - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-ai-assistant/overlays/prod/kustomization.yaml") - if not os.path.exists(kustomization_path): - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-ai-assistant/base/kustomization.yaml") - else: - kustomization_path = os.path.join(args.repo, "apps/nxtgauge-backend-rust/overlays/prod/kustomization.yaml") - - if not os.path.exists(kustomization_path): - print(f"[ERROR] Kustomization file not found: {kustomization_path}") - sys.exit(0) # Exit 0 per workflow requirement - - print(f"Updating {kustomization_path} for service {args.service}") - - if not update_kustomization(kustomization_path, args.service, args.sha): - sys.exit(0) # Exit 0 per workflow requirement - - # Git add, commit, push - commit_msg = args.message or f"chore: deploy {args.service}@{args.sha}" - - run(["git", "add", "-A"], cwd=args.repo) - code, stdout, stderr = run(["git", "diff", "--cached", "--stat"], cwd=args.repo) - - if not stdout.strip(): - print("[INFO] No changes to commit") - sys.exit(0) - - print(f"Changes to commit:\n{stdout}") - - run(["git", "commit", "-m", commit_msg], cwd=args.repo) - code, stdout, stderr = run(["git", "push"], cwd=args.repo) - - if code != 0: - print(f"[ERROR] Push failed: {stderr}") - else: - print(f"[OK] Pushed update to gitops repo") - - sys.exit(0) # Always exit 0 per workflow requirement - - -if __name__ == "__main__": - main() diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 81f49d0..7241470 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -1,4 +1,4 @@ -name: build-and-push +name: build-and-release on: push: @@ -6,48 +6,56 @@ on: - main - high-performance +concurrency: + group: admin-solid-build-${{ github.ref }} + cancel-in-progress: true + jobs: build: - runs-on: ubuntu-latest - env: - DOCKER_HOST: unix:///var/run/docker.sock + runs-on: self-hosted steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Docker Buildx run: | - export DOCKER_HOST=unix:///var/run/docker.sock + set -euo pipefail docker version - docker buildx create --use || true + docker buildx create --use --name nxtgauge-builder || docker buildx use nxtgauge-builder docker buildx inspect --bootstrap - - name: Login to Registry + - name: Login to registry env: REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} run: | set -euo pipefail - export DOCKER_HOST=unix:///var/run/docker.sock - SHA="$(git rev-parse HEAD)" test -n "$REGISTRY_HOSTPORT" - echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOSTPORT" -u "$REGISTRY_USERNAME" --password-stdin + printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOSTPORT" -u "$REGISTRY_USERNAME" --password-stdin - - name: Build and push + - name: Build and push image env: REGISTRY_HOSTPORT: ${{ secrets.REGISTRY_HOSTPORT }} + SHA: ${{ github.sha }} run: | set -euo pipefail - export DOCKER_HOST=unix:///var/run/docker.sock - SHA="$(git rev-parse HEAD)" + metadata_file="/tmp/admin-solid-metadata.json" + image_ref="$REGISTRY_HOSTPORT/nxtgauge-admin-solid:$SHA" + docker buildx build --push \ + --metadata-file "$metadata_file" \ -f Dockerfile \ - -t "$REGISTRY_HOSTPORT/nxtgauge-admin-solid:${SHA}" \ - -t "$REGISTRY_HOSTPORT/nxtgauge-admin-solid:high-performance-latest" \ + -t "$image_ref" \ . - - name: Prune old image tags (keep latest 1 SHA) + digest="$(grep -o '"containerimage.digest":"sha256:[^"]*"' "$metadata_file" | cut -d'"' -f4)" + test -n "$digest" + printf '%s@%s\n' "$REGISTRY_HOSTPORT/nxtgauge-admin-solid" "$digest" > /tmp/admin-solid-image-ref.txt + + - name: Prune old SHA tags if: success() continue-on-error: true env: @@ -61,36 +69,37 @@ jobs: --repo "nxtgauge-admin-solid" \ --username "$REGISTRY_USERNAME" \ --password "$REGISTRY_PASSWORD" \ - --keep 1 + --keep 2 - - name: Update GitOps and trigger deployment - if: success() - continue-on-error: true + - name: Update GitOps release env: GITEOPS_REPO: ${{ secrets.GITEOPS_REPO }} GITEOPS_SSH_KEY: ${{ secrets.GITEOPS_SSH_KEY }} + SHA: ${{ github.sha }} run: | set -euo pipefail - SHA="$(git rev-parse HEAD)" + test -n "$GITEOPS_REPO" + test -n "$GITEOPS_SSH_KEY" - if [ -z "$GITEOPS_REPO" ]; then - echo "GITEOPS_REPO secret not set, skipping GitOps update" - exit 0 - fi + mkdir -p ~/.ssh + printf '%s\n' "$GITEOPS_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null GITEOPS_DIR=$(mktemp -d) git clone "$GITEOPS_REPO" "$GITEOPS_DIR" cd "$GITEOPS_DIR" - mkdir -p ~/.ssh - echo "$GITEOPS_SSH_KEY" > ~/.ssh/id_ed25519 - chmod 600 ~/.ssh/id_ed25519 - ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null + image_ref="$(cat /tmp/admin-solid-image-ref.txt)" + ./scripts/set-app-release.sh admin-solid "$image_ref" - python3 .forgejo/scripts/update-gitops.py \ - --repo "$GITEOPS_DIR" \ - --service "admin-solid" \ - --sha "${SHA}" \ - --message "chore: deploy admin-solid@${SHA}" + if git diff --quiet; then + echo "GitOps repo already up to date." + exit 0 + fi - rm -rf "$GITEOPS_DIR" + git config user.name "forgejo-actions[bot]" + git config user.email "forgejo-actions@ci.nxtgauge.com" + git add apps scripts/set-app-release.sh + git commit -m "chore(gitops): deploy admin-solid@${SHA}" + git push