name: Promote to Production (manual) # Second approval gate of the deploy model: run this workflow from the # Actions UI after testing the feature on staging. It refuses commits # whose staging E2E gate is not green. See docs/DEPLOY.md. on: workflow_dispatch: inputs: sha: description: >- Commit SHA on main to promote (full or >=7 chars). Leave empty to promote the latest main commit. required: false default: "" env: REGISTRY: privaterepo.sitaru.org BACKEND_IMAGE_NAME: ${{ gitea.repository }}-backend FRONTEND_IMAGE_NAME: ${{ gitea.repository }}-frontend PIPELINE_IMAGE_NAME: ${{ gitea.repository }}-pipeline jobs: promote-prod: name: Promote approved commit to Production runs-on: ubuntu-latest steps: - name: Resolve target SHA id: resolve run: | SHA_INPUT="${{ gitea.event.inputs.sha }}" if [ -z "$SHA_INPUT" ]; then SHA_INPUT="${{ gitea.sha }}" fi # Normalise to the full sha via the API so short inputs work FULL_SHA=$(curl -fsS \ -H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \ "https://${REGISTRY}/api/v1/repos/${{ gitea.repository }}/git/commits/${SHA_INPUT}" \ | python3 -c "import json,sys; print(json.load(sys.stdin)['sha'])") SHORT_SHA="sha-$(echo "$FULL_SHA" | cut -c1-7)" echo "full=$FULL_SHA" >> "$GITHUB_OUTPUT" echo "short=$SHORT_SHA" >> "$GITHUB_OUTPUT" echo "Promoting $FULL_SHA (images tagged $SHORT_SHA)" - name: Verify the staging E2E gate passed for this commit run: | STATUS_JSON=$(curl -fsS \ -H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \ "https://${REGISTRY}/api/v1/repos/${{ gitea.repository }}/commits/${{ steps.resolve.outputs.full }}/status") echo "$STATUS_JSON" | python3 -c " import json, sys d = json.load(sys.stdin) ok = [s for s in d.get('statuses', []) if 'E2E Journeys against Staging' in s.get('context', '') and s.get('status') == 'success'] if not ok: print('REFUSED: no successful \"E2E Journeys against Staging\" status on this commit.') print('Contexts found:', [s.get('context') for s in d.get('statuses', [])]) sys.exit(1) print('E2E gate verified green for this commit.') " - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Gitea Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ gitea.actor }} password: ${{ secrets.REGISTRY_TOKEN }} - name: Retag approved images as prod (keeping rollback pointer) run: | SHORT_SHA="${{ steps.resolve.outputs.short }}" for IMAGE in \ "${REGISTRY}/${BACKEND_IMAGE_NAME}" \ "${REGISTRY}/${FRONTEND_IMAGE_NAME}" \ "${REGISTRY}/${PIPELINE_IMAGE_NAME}"; do # Keep a rollback pointer before moving :prod docker buildx imagetools create -t "${IMAGE}:prod-previous" "${IMAGE}:prod" || true docker buildx imagetools create -t "${IMAGE}:prod" "${IMAGE}:${SHORT_SHA}" echo "Promoted ${IMAGE}:${SHORT_SHA} -> :prod" done - name: Trigger production stack update run: curl -fsSk -X POST "${{ secrets.PORTAINER_PROD_WEBHOOK }}" - name: Wait for production to become healthy run: | echo "Polling ${PROD_BASE_URL} for up to 5 minutes..." for i in $(seq 1 60); do if curl -fsS -o /dev/null --max-time 10 "${PROD_BASE_URL}/"; then echo "Production is up (attempt $i)" exit 0 fi sleep 5 done echo "Production did not become healthy in time" >&2 exit 1 env: PROD_BASE_URL: ${{ secrets.PROD_BASE_URL }}