apps/leads implemented its own, independent "lead request" system (POST /api/leads, /api/lead-requests/send, accept/reject) with a schema that never matched the live one (message vs remarks, no professional_user_id, accepted_at/rejected_at instead of resolved_at — see 20260721030000_create_lead_requests's commit message). Confirmed unreachable: the frontend's live flows use apps/customers' /api/customers/requirements and each profession's /leads/request (crates/contracts::profession_shared), never anything under apps/leads' own paths. Its /api/lead-requests/* endpoints weren't even reachable through the gateway (wrong prefix, never matched /api/leads or /api/admin/leads). Removed: - apps/leads/ entirely, and its Cargo.toml workspace membership - the `leads` docker-compose service, its LEADS_SERVICE_URL env var on gateway, and gateway's depends_on entry - the `leads` entry from both CI build matrices (.gitea/.forgejo) - gateway's leads_url field/routing branch — gateway no longer hard- requires LEADS_SERVICE_URL to boot (.expect() would have panicked once the service was gone); /api/admin/leads now falls through to the customers service, which already had a matching (previously shadowed) branch for it NOTE: this service may still have a live Deployment/Service in nxtgauge-gitops (a separate repo not touched here) — that manifest should be removed too, or the next deploy will reference an image that no CI job builds anymore.
209 lines
8.3 KiB
YAML
209 lines
8.3 KiB
YAML
name: build-and-release
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- high-performance
|
|
|
|
concurrency:
|
|
group: backend-build-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: docker-ready
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 6
|
|
matrix:
|
|
service:
|
|
- gateway
|
|
- users
|
|
- companies
|
|
- jobs
|
|
- job-seekers
|
|
- customers
|
|
- payments
|
|
- employees
|
|
- photographers
|
|
- makeup-artists
|
|
- tutors
|
|
- developers
|
|
- video-editors
|
|
- graphic-designers
|
|
- social-media-managers
|
|
- fitness-trainers
|
|
- catering-services
|
|
- ugc-content-creators
|
|
- cron
|
|
env:
|
|
DOCKER_BUILDKIT: "1"
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
# Static matrix (all 20 services every push) instead of a single job
|
|
# looping sequentially - up to 3 run concurrently (one per runner pod;
|
|
# each runner's capacity is 1). This step is each job's own quick
|
|
# "do I actually need to do anything" check, so unaffected services
|
|
# skip in a couple of seconds rather than sitting in a shared queue.
|
|
- name: Check if this service needs building
|
|
id: check
|
|
run: |
|
|
set -euo pipefail
|
|
service="${{ matrix.service }}"
|
|
svc_dir="$(echo "$service" | tr '-' '_')"
|
|
|
|
if git rev-parse --verify HEAD^ >/dev/null 2>&1; then
|
|
CHANGED_FILES="$(git diff --name-only HEAD^ HEAD)"
|
|
else
|
|
CHANGED_FILES="$(git ls-files)"
|
|
fi
|
|
LAST_COMMIT_MSG="$(git log -1 --pretty=%B | tr '\n' ' ')"
|
|
|
|
build=false
|
|
if echo "$LAST_COMMIT_MSG" | grep -Eiq 'trigger build|force build|rebuild all'; then
|
|
build=true
|
|
elif echo "$CHANGED_FILES" | grep -Eq '^(\.forgejo/workflows/|Dockerfile|Cargo\.toml|Cargo\.lock|crates/|scripts/)'; then
|
|
build=true
|
|
elif echo "$CHANGED_FILES" | grep -q "^apps/${svc_dir}/"; then
|
|
build=true
|
|
fi
|
|
|
|
echo "build=$build" >> "$GITHUB_OUTPUT"
|
|
if [ "$build" = "false" ]; then
|
|
echo "No changes relevant to $service - skipping."
|
|
fi
|
|
|
|
- name: Point DOCKER_HOST at this container's own gateway
|
|
if: steps.check.outputs.build == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
# 127.0.0.1 doesn't work: the job container is nested one level
|
|
# inside the runner pod's dind sidecar, so its own loopback isn't
|
|
# the sidecar's. --add-host=host.docker.internal:host-gateway is
|
|
# not being honored by this runner (tried via both global
|
|
# container.options and a per-job container: block — neither
|
|
# resolved), so read the container's actual default-route gateway
|
|
# directly instead, which is the dind engine that spawned it.
|
|
# Read directly from /proc/net/route instead of relying on the `ip`
|
|
# or `route` CLI tools being installed in the job image.
|
|
GATEWAY="$(awk '$2 == "00000000" {print $3}' /proc/net/route | head -1 | \
|
|
sed -E 's/(..)(..)(..)(..)/0x\4 0x\3 0x\2 0x\1/' | \
|
|
{ read -r a b c d; printf '%d.%d.%d.%d' "$a" "$b" "$c" "$d"; })"
|
|
echo "Detected docker host gateway: $GATEWAY"
|
|
echo "DOCKER_HOST=tcp://$GATEWAY:2375" >> "$GITHUB_ENV"
|
|
|
|
- name: Set up Docker Buildx
|
|
if: steps.check.outputs.build == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
docker version
|
|
# Same builder name on every node is safe: each runner has
|
|
# capacity 1, so only one job ever touches a given node's dind
|
|
# engine at a time - reusing the name lets its build cache persist
|
|
# (and warm up) across services scheduled on that node over time.
|
|
docker buildx create --use --name nxtgauge-builder || docker buildx use nxtgauge-builder
|
|
docker buildx inspect --bootstrap
|
|
|
|
- name: Login to registry
|
|
if: steps.check.outputs.build == 'true'
|
|
env:
|
|
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST || 'ci.nxtgauge.com' }}
|
|
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
printf '%s' "$REGISTRY_PASSWORD" | docker login "$REGISTRY_HOST" -u "$REGISTRY_USERNAME" --password-stdin
|
|
|
|
- name: Build and push
|
|
if: steps.check.outputs.build == 'true'
|
|
env:
|
|
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST || 'ci.nxtgauge.com' }}
|
|
REGISTRY_NAMESPACE: ${{ secrets.REGISTRY_NAMESPACE || 'ashwin' }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
service="${{ matrix.service }}"
|
|
metadata_file="/tmp/${service}-metadata.json"
|
|
image_ref="$REGISTRY_HOST/$REGISTRY_NAMESPACE/nxtgauge-rust-${service}:${SHA}"
|
|
|
|
docker buildx build --push \
|
|
--metadata-file "$metadata_file" \
|
|
-f Dockerfile.simple \
|
|
--build-arg SERVICE_NAME="$service" \
|
|
-t "$image_ref" \
|
|
.
|
|
|
|
# buildx writes the metadata file pretty-printed (space after the
|
|
# colon), which the old compact-JSON-only pattern never matched -
|
|
# tolerate optional whitespace and extract the digest directly.
|
|
digest="$(grep -o '"containerimage\.digest"[[:space:]]*:[[:space:]]*"sha256:[^"]*"' "$metadata_file" | grep -o 'sha256:[^"]*')"
|
|
if [ -z "$digest" ]; then
|
|
echo "Failed to determine digest for $service" >&2
|
|
exit 1
|
|
fi
|
|
echo "DIGEST=$digest" >> "$GITHUB_ENV"
|
|
|
|
- name: Update GitOps release state
|
|
if: steps.check.outputs.build == 'true'
|
|
env:
|
|
GITOPS_SERVER: ${{ secrets.GITOPS_SERVER || 'ci.nxtgauge.com' }}
|
|
GITOPS_OWNER: ${{ secrets.GITOPS_OWNER || 'ashwin' }}
|
|
GITOPS_REPO: ${{ secrets.GITOPS_REPO || 'nxtgauge-gitops' }}
|
|
GITOPS_BRANCH: ${{ secrets.GITOPS_BRANCH || 'main' }}
|
|
# The gitops repo now lives on Forgejo (ci.nxtgauge.com), not
|
|
# GitHub - GITOPS_PUSH_USERNAME/GITOPS_PUSH_TOKEN were never
|
|
# actually configured as repo secrets (only the leftover
|
|
# GITOPS_GITHUB_* pair and GITOPS_PAT exist). Use GITOPS_PAT,
|
|
# which matches the current Forgejo-hosted repo.
|
|
GITOPS_PAT: ${{ secrets.GITOPS_PAT }}
|
|
SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
test -n "${GITOPS_PAT:-}" || { echo "GITOPS_PAT is empty"; exit 1; }
|
|
service="${{ matrix.service }}"
|
|
|
|
git clone "https://forgejo-actions:${GITOPS_PAT}@${GITOPS_SERVER}/${GITOPS_OWNER}/${GITOPS_REPO}.git" /tmp/nxtgauge-gitops
|
|
cd /tmp/nxtgauge-gitops
|
|
|
|
# Up to 9 of these jobs can be pushing to the same gitops branch at
|
|
# once now - retry with a fresh pull+rebase on a non-fast-forward
|
|
# rejection instead of assuming we're the only writer.
|
|
pushed=false
|
|
for attempt in 1 2 3 4 5 6 7 8; do
|
|
git checkout "$GITOPS_BRANCH"
|
|
./scripts/set-backend-rust-release.sh "$service" "$DIGEST"
|
|
|
|
if git diff --quiet; then
|
|
echo "GitOps repo already up to date for $service."
|
|
pushed=true
|
|
break
|
|
fi
|
|
|
|
git config user.name "forgejo-actions[bot]"
|
|
git config user.email "forgejo-actions@ci.nxtgauge.com"
|
|
git add \
|
|
apps/nxtgauge-backend-rust/overlays/prod/backend-release-state.tsv \
|
|
apps/nxtgauge-backend-rust/overlays/prod/release-patches.yaml \
|
|
apps/nxtgauge-backend-rust/overlays/prod/disabled-deployments.yaml
|
|
git commit -m "chore(gitops): update ${service} image for ${SHA}"
|
|
|
|
if git push origin "HEAD:${GITOPS_BRANCH}"; then
|
|
pushed=true
|
|
break
|
|
fi
|
|
|
|
echo "Push rejected (attempt $attempt/8), pulling latest and retrying..."
|
|
git fetch origin "$GITOPS_BRANCH"
|
|
git reset --hard "origin/${GITOPS_BRANCH}"
|
|
sleep $((attempt * 2))
|
|
done
|
|
|
|
if [ "$pushed" != true ]; then
|
|
echo "Failed to push gitops update for $service after all retries" >&2
|
|
exit 1
|
|
fi
|