|
56 | 56 | # whole point of the decoupling: rapid pushes to main no longer cancel |
57 | 57 | # in-flight builds. |
58 | 58 |
|
59 | | -env: |
60 | | - RAILWAY_ENV_ID: "b14919f4-6417-429f-848d-c6ae2201e04f" |
| 59 | +# Top-level env intentionally empty: env IDs are sourced from the |
| 60 | +# showcase/scripts/railway-envs.ts SSOT by the redeploy-staging job. |
61 | 61 |
|
62 | 62 | permissions: |
63 | 63 | contents: read |
@@ -416,33 +416,60 @@ jobs: |
416 | 416 | ghcr.io/copilotkit/${{ matrix.service.image }}:${{ github.sha }} |
417 | 417 | build-args: ${{ steps.build-args.outputs.args }} |
418 | 418 |
|
419 | | - - name: Trigger Railway redeploy |
420 | | - if: matrix.service.railway_id != '' |
| 419 | + redeploy-staging: |
| 420 | + name: Trigger Railway staging redeploy |
| 421 | + needs: [detect-changes, build] |
| 422 | + # Run if at least one service was in the matrix AND the build job |
| 423 | + # was not outright cancelled or skipped. We tolerate per-slot build |
| 424 | + # failures: redeploy what we did push (the script only redeploys |
| 425 | + # requested services, and slots that failed to push will simply |
| 426 | + # re-pull the previous :latest — which is a no-op). However, if the |
| 427 | + # build job was SKIPPED entirely (e.g. the verify-image-refs prod |
| 428 | + # digest-pin gate failed, blocking build), suppress the redeploy: |
| 429 | + # nothing was pushed and there is no reason to redeploy staging. |
| 430 | + # Partial build failures (fail-fast: false) still surface as |
| 431 | + # needs.build.result == 'failure', so the intent of redeploying what |
| 432 | + # did get pushed is preserved. |
| 433 | + if: ${{ !cancelled() && needs.detect-changes.outputs.has_changes == 'true' && needs.build.result != 'skipped' && needs.build.result != 'cancelled' }} |
| 434 | + runs-on: ubuntu-latest |
| 435 | + timeout-minutes: 5 |
| 436 | + permissions: |
| 437 | + contents: read |
| 438 | + steps: |
| 439 | + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 |
| 440 | + with: |
| 441 | + persist-credentials: false |
| 442 | + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 |
| 443 | + with: |
| 444 | + node-version: 22.x |
| 445 | + - name: Compute changed-service list from build matrix |
| 446 | + id: changed |
421 | 447 | env: |
422 | | - RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} |
423 | | - SERVICE_ID: ${{ matrix.service.railway_id }} |
424 | | - ENV_ID: ${{ env.RAILWAY_ENV_ID }} |
| 448 | + MATRIX_JSON: ${{ needs.detect-changes.outputs.matrix }} |
425 | 449 | run: | |
426 | | - # Trigger Railway to pull the freshly-pushed :latest image. |
427 | | - # Not all services have environmentPatchCommit auto-update |
428 | | - # configured, so an explicit redeploy is the reliable path. |
429 | | - RESULT=$(curl -s --retry 2 --retry-all-errors --retry-delay 1 \ |
430 | | - --fail-with-body \ |
431 | | - -X POST "https://backboard.railway.com/graphql/v2" \ |
432 | | - -H "Authorization: Bearer $RAILWAY_TOKEN" \ |
433 | | - -H "Content-Type: application/json" \ |
434 | | - -d "{\"query\":\"mutation { serviceInstanceRedeploy(serviceId: \\\"$SERVICE_ID\\\", environmentId: \\\"$ENV_ID\\\") }\"}") |
435 | | - ERRORS=$(echo "$RESULT" | jq -r '.errors[]?.message // empty') |
436 | | - if [ -n "$ERRORS" ]; then |
437 | | - echo "::error::Railway redeploy failed for ${{ matrix.service.dispatch_name }}: $ERRORS" |
438 | | - exit 1 |
439 | | - fi |
440 | | - OK=$(echo "$RESULT" | jq -r '.data.serviceInstanceRedeploy // false') |
441 | | - if [ "$OK" != "true" ]; then |
442 | | - echo "::error::Railway redeploy did not confirm success for ${{ matrix.service.dispatch_name }}: $RESULT" |
443 | | - exit 1 |
| 450 | + # detect-changes.outputs.matrix is a JSON array of objects, each with |
| 451 | + # a dispatch_name field. We extract just those names into a CSV the |
| 452 | + # redeploy-env.ts --services flag understands. resolveTargetServices() |
| 453 | + # in the script accepts either SSOT keys OR dispatch_names. |
| 454 | + set -euo pipefail |
| 455 | + csv="$(echo "$MATRIX_JSON" | jq -r '[.[] | .dispatch_name] | join(",")')" |
| 456 | + if [ -z "$csv" ]; then |
| 457 | + echo "No services in matrix — skipping redeploy." |
| 458 | + echo "services=" >> "$GITHUB_OUTPUT" |
| 459 | + else |
| 460 | + echo "services=$csv" >> "$GITHUB_OUTPUT" |
444 | 461 | fi |
445 | | - echo "Railway redeploy triggered for ${{ matrix.service.dispatch_name }}" |
| 462 | + echo "Computed services CSV: $csv" |
| 463 | + - name: Redeploy changed services in staging |
| 464 | + if: steps.changed.outputs.services != '' |
| 465 | + env: |
| 466 | + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} |
| 467 | + SERVICES_CSV: ${{ steps.changed.outputs.services }} |
| 468 | + run: | |
| 469 | + # Staging is non-blocking by design: the script always exits 0 |
| 470 | + # and writes per-service failures into $GITHUB_STEP_SUMMARY. The |
| 471 | + # verify-deploy workflow is the real release gate. |
| 472 | + npx tsx showcase/scripts/redeploy-env.ts staging --services "$SERVICES_CSV" |
446 | 473 |
|
447 | 474 | notify: |
448 | 475 | name: Notify on failure |
|
0 commit comments