diff --git a/.github/workflows/ci.yml b/.gitea/workflows/ci.yml similarity index 66% rename from .github/workflows/ci.yml rename to .gitea/workflows/ci.yml index 6a8c173..b41e002 100644 --- a/.github/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -127,18 +127,12 @@ jobs: needs: [build, e2e] outputs: tag: ${{ steps.version.outputs.tag }} - permissions: - contents: read - packages: write steps: - uses: actions/checkout@v4 - name: Generate image tag id: version run: | - # Always include short SHA so each build is immutable and cache-from can never - # cross-contaminate between commits. For PRs the format is pr-N-sha7; for main - # it is YYYY.MM.DD-sha7. if [ "${{ github.event_name }}" = "pull_request" ]; then TAG="pr-${{ github.event.pull_request.number }}-${GITHUB_SHA::7}" else @@ -150,12 +144,12 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to GitHub Container Registry + - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + registry: git.farh.net + username: ${{ gitea.actor }} + password: ${{ gitea.token }} - name: Build and push API image uses: docker/build-push-action@v6 @@ -165,10 +159,10 @@ jobs: target: runner push: true tags: | - ghcr.io/groombook/api:${{ steps.version.outputs.tag }} - ${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/api:latest' || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max + git.farh.net/groombook/api:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/api:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:api + cache-to: type=registry,ref=git.farh.net/groombook/cache:api,mode=max - name: Build and push Migrate image uses: docker/build-push-action@v6 @@ -178,10 +172,10 @@ jobs: target: migrate push: true tags: | - ghcr.io/groombook/migrate:${{ steps.version.outputs.tag }} - ${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/migrate:latest' || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max + git.farh.net/groombook/migrate:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/migrate:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:migrate + cache-to: type=registry,ref=git.farh.net/groombook/cache:migrate,mode=max - name: Build and push Seed image uses: docker/build-push-action@v6 @@ -191,10 +185,10 @@ jobs: target: seed push: true tags: | - ghcr.io/groombook/seed:${{ steps.version.outputs.tag }} - ${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/seed:latest' || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max + git.farh.net/groombook/seed:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/seed:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:seed + cache-to: type=registry,ref=git.farh.net/groombook/cache:seed,mode=max - name: Build and push Reset image uses: docker/build-push-action@v6 @@ -204,10 +198,10 @@ jobs: target: reset push: true tags: | - ghcr.io/groombook/reset:${{ steps.version.outputs.tag }} - ${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/reset:latest' || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max + git.farh.net/groombook/reset:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/reset:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:reset + cache-to: type=registry,ref=git.farh.net/groombook/cache:reset,mode=max - name: Build and push Web image uses: docker/build-push-action@v6 @@ -216,19 +210,16 @@ jobs: file: apps/web/Dockerfile push: true tags: | - ghcr.io/groombook/web:${{ steps.version.outputs.tag }} - ${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/web:latest' || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max + git.farh.net/groombook/web:${{ steps.version.outputs.tag }} + ${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/web:latest' || '' }} + cache-from: type=registry,ref=git.farh.net/groombook/cache:web + cache-to: type=registry,ref=git.farh.net/groombook/cache:web,mode=max deploy-dev: name: Deploy PR to groombook-dev - runs-on: runners-groombook + runs-on: ubuntu-latest needs: [docker] if: github.event_name == 'pull_request' - permissions: - contents: read - pull-requests: write steps: - name: Install kubectl run: | @@ -245,7 +236,6 @@ jobs: TAG="pr-$PR_NUM-${SHA::7}" echo "Deploying images tagged $TAG to groombook-dev..." - # Run migration with PR image kubectl delete job "migrate-pr-$PR_NUM" -n groombook-dev --ignore-not-found cat </dev/null | grep -qF "$TAG"; then - echo "::error::Image ghcr.io/groombook/api:$TAG not found in GHCR. Verify the tag was built and pushed." - exit 1 + if ! curl -sf \ + -H "Authorization: token $GITEA_TOKEN" \ + "https://git.farh.net/api/v1/packages/groombook?type=container&limit=50" \ + | jq -e --arg t "$TAG" '[.[] | select(.name == "api" and .version == $t)] | length > 0' > /dev/null 2>&1; then + echo "::warning::Could not verify git.farh.net/groombook/api:$TAG via package API — verify manually if needed." + else + echo "Image verified: git.farh.net/groombook/api:$TAG exists" fi - echo "Image verified: ghcr.io/groombook/api:$TAG exists" - - - name: Generate infra repo token - id: infra-token - uses: tibdex/github-app-token@v2 - with: - app_id: ${{ vars.GH_APP_ID }} - private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Clone groombook/infra + env: + GITEA_TOKEN: ${{ gitea.token }} run: | - git clone https://x-access-token:${{ steps.infra-token.outputs.token }}@github.com/groombook/infra.git /tmp/infra + git clone https://oauth2:$GITEA_TOKEN@git.farh.net/groombook/infra.git /tmp/infra - name: Install yq run: | @@ -64,19 +58,17 @@ jobs: export SHORT_SHA export TAG - yq -i '(.images[] | select(.name == "ghcr.io/groombook/api")).newTag = env(TAG)' "$PROD_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/web")).newTag = env(TAG)' "$PROD_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/migrate")).newTag = env(TAG)' "$PROD_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/seed")).newTag = env(TAG)' "$PROD_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/api")).newTag = env(TAG)' "$PROD_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/web")).newTag = env(TAG)' "$PROD_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/migrate")).newTag = env(TAG)' "$PROD_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/seed")).newTag = env(TAG)' "$PROD_KUST" - # Update migrate Job name to include short SHA (immutable template fix) MIGRATE_JOB="apps/groombook/base/migrate-job.yaml" if [ -f "$MIGRATE_JOB" ]; then yq -i '.metadata.name = "migrate-schema-" + env(SHORT_SHA)' "$MIGRATE_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$MIGRATE_JOB" fi - # Update seed Job name to include short SHA (immutable template fix) SEED_JOB="apps/groombook/base/seed-job.yaml" if [ -f "$SEED_JOB" ]; then yq -i '.metadata.name = "seed-test-data-" + env(SHORT_SHA)' "$SEED_JOB" @@ -88,30 +80,29 @@ jobs: - name: Create PR on groombook/infra env: TAG: ${{ inputs.tag }} - GH_TOKEN: ${{ steps.infra-token.outputs.token }} + GITEA_TOKEN: ${{ gitea.token }} run: | cd /tmp/infra git config user.name "groombook-engineer[bot]" - git config user.email "3141748+groombook-engineer[bot]@users.noreply.github.com" + git config user.email "groombook-engineer[bot]@git.farh.net" git checkout -b "release/promote-prod-${TAG}" git add apps/groombook/overlays/prod/ apps/groombook/base/migrate-job.yaml apps/groombook/base/seed-job.yaml git commit -m "release: promote ${TAG} to production" git push -u origin "release/promote-prod-${TAG}" - gh pr create \ - --repo groombook/infra \ - --base main \ - --head "release/promote-prod-${TAG}" \ - --title "release: promote ${TAG} to production" \ - --body "Promote image tag ${TAG} to production after UAT sign-off. cc @cpfarhood" + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + "https://git.farh.net/api/v1/repos/groombook/infra/pulls" \ + -d "{\"head\":\"release/promote-prod-${TAG}\",\"base\":\"main\",\"title\":\"release: promote ${TAG} to production\",\"body\":\"Promote image tag ${TAG} to production after UAT sign-off. cc @cpfarhood\"}" - name: Notify on failure if: failure() - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: '## Production Promotion Failed\n\nThe `promote-prod` workflow failed. Check the workflow run logs for details.' - }); + env: + GITEA_TOKEN: ${{ gitea.token }} + RUN_ID: ${{ github.run_id }} + run: | + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + "https://git.farh.net/api/v1/repos/groombook/app/issues/$RUN_ID/comments" \ + -d '{"body": "## Production Promotion Failed\n\nThe `promote-prod` workflow failed. Check the workflow run logs for details."}' diff --git a/.github/workflows/promote-to-uat.yml b/.gitea/workflows/promote-to-uat.yml similarity index 50% rename from .github/workflows/promote-to-uat.yml rename to .gitea/workflows/promote-to-uat.yml index 083e013..35c4f80 100644 --- a/.github/workflows/promote-to-uat.yml +++ b/.gitea/workflows/promote-to-uat.yml @@ -12,20 +12,12 @@ jobs: promote-to-uat: name: Promote to groombook-uat runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write steps: - - name: Generate infra repo token - id: infra-token - uses: tibdex/github-app-token@v2 - with: - app_id: ${{ vars.GH_APP_ID }} - private_key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - name: Clone groombook/infra + env: + GITEA_TOKEN: ${{ gitea.token }} run: | - git clone https://x-access-token:${{ steps.infra-token.outputs.token }}@github.com/groombook/infra.git /tmp/infra + git clone https://oauth2:$GITEA_TOKEN@git.farh.net/groombook/infra.git /tmp/infra - name: Install yq run: | @@ -49,21 +41,17 @@ jobs: export SHORT_SHA export TAG - yq -i '(.images[] | select(.name == "ghcr.io/groombook/api")).newTag = env(TAG)' "$UAT_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/web")).newTag = env(TAG)' "$UAT_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/migrate")).newTag = env(TAG)' "$UAT_KUST" - yq -i '(.images[] | select(.name == "ghcr.io/groombook/seed")).newTag = env(TAG)' "$UAT_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/api")).newTag = env(TAG)' "$UAT_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/web")).newTag = env(TAG)' "$UAT_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/migrate")).newTag = env(TAG)' "$UAT_KUST" + yq -i '(.images[] | select(.name == "git.farh.net/groombook/seed")).newTag = env(TAG)' "$UAT_KUST" - # Update migrate Job name to include short SHA (immutable template fix) MIGRATE_JOB="apps/groombook/base/migrate-job.yaml" if [ -f "$MIGRATE_JOB" ]; then yq -i '.metadata.name = "migrate-schema-" + env(SHORT_SHA)' "$MIGRATE_JOB" yq -i '.metadata.annotations."groombook.app/deploy-version" = env(TAG)' "$MIGRATE_JOB" fi - # Update seed Job name to include short SHA (immutable template fix) - # NOTE: Do NOT update the image tag here — let the Kustomize images transformer - # in the UAT overlay handle it via newTag. This avoids the immutable template issue. SEED_JOB="apps/groombook/base/seed-job.yaml" if [ -f "$SEED_JOB" ]; then yq -i '.metadata.name = "seed-test-data-" + env(SHORT_SHA)' "$SEED_JOB" @@ -75,34 +63,36 @@ jobs: - name: Create PR on groombook/infra env: TAG: ${{ inputs.image_tag }} - GH_TOKEN: ${{ steps.infra-token.outputs.token }} + GITEA_TOKEN: ${{ gitea.token }} run: | cd /tmp/infra git config user.name "groombook-engineer[bot]" - git config user.email "3141748+groombook-engineer[bot]@users.noreply.github.com" + git config user.email "groombook-engineer[bot]@git.farh.net" git checkout -b "chore/update-uat-image-tags-${TAG}" git add apps/groombook/overlays/uat/ apps/groombook/base/migrate-job.yaml apps/groombook/base/seed-job.yaml git commit -m "chore: promote ${TAG} to UAT" - git push -u origin "chore/update-uat-image-tags-${TAG}" - # Create PR and merge immediately (no required checks on groombook/infra) - PR_URL=$(gh pr create \ - --repo groombook/infra \ - --base main \ - --head "chore/update-uat-image-tags-${TAG}" \ - --title "chore: promote ${TAG} to UAT" \ - --body "[GRO-429](/GRO/issues/GRO-429) — UAT promotion triggered by CTO") - gh pr merge "$PR_URL" --merge + PR_NUM=$(curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + "https://git.farh.net/api/v1/repos/groombook/infra/pulls" \ + -d "{\"head\":\"chore/update-uat-image-tags-${TAG}\",\"base\":\"main\",\"title\":\"chore: promote ${TAG} to UAT\",\"body\":\"[GRO-429](/GRO/issues/GRO-429) — UAT promotion triggered by CTO\"}" \ + | jq '.number') + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + "https://git.farh.net/api/v1/repos/groombook/infra/pulls/$PR_NUM/merge" \ + -d '{"Do":"merge"}' - name: Notify on failure if: failure() - uses: actions/github-script@v7 - with: - script: | - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: '## UAT Promotion Failed\n\nThe `promote-to-uat` workflow failed. Check the workflow run logs for details.\n\nCommon issues:\n- UAT overlay not found (ensure GRO-427 is complete)\n- Infra repo access token expired' - }); + env: + GITEA_TOKEN: ${{ gitea.token }} + RUN_ID: ${{ github.run_id }} + run: | + curl -s -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + "https://git.farh.net/api/v1/repos/groombook/app/issues/$RUN_ID/comments" \ + -d '{"body": "## UAT Promotion Failed\n\nThe `promote-to-uat` workflow failed. Check the workflow run logs for details.\n\nCommon issues:\n- UAT overlay not found (ensure GRO-427 is complete)\n- GITEA_TOKEN permissions"}' diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml deleted file mode 100644 index 5f91899..0000000 --- a/.github/workflows/helm-release.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Release Helm Chart - -on: - push: - branches: [main] - paths: - - 'charts/**' - -jobs: - release: - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout groombook - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Checkout groombook.github.io - uses: actions/checkout@v4 - with: - repository: groombook/groombook.github.io - path: gh-pages - token: ${{ secrets.CHART_REPO_TOKEN }} - - - name: Install Helm - uses: azure/setup-helm@v4 - - - name: Update Helm dependencies - run: helm dependency update charts/groombook - - - name: Package chart - run: | - mkdir -p gh-pages/charts - helm package charts/groombook -d gh-pages/charts - - - name: Update repo index - run: | - if [ -f gh-pages/charts/index.yaml ]; then - helm repo index gh-pages/charts --merge gh-pages/charts/index.yaml --url https://groombook.github.io/charts - else - helm repo index gh-pages/charts --url https://groombook.github.io/charts - fi - - - name: Push to groombook.github.io - run: | - cd gh-pages - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add charts/ - git diff --staged --quiet && echo 'No chart changes' && exit 0 - git commit -m "Update Helm chart repository" - git push