diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfdb6ef8..a4d582fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,9 @@ name: CI +run-name: >- + ${{ github.event_name == 'pull_request' && format('PR-CI #{0}', github.event.pull_request.number) + || github.event_name == 'merge_group' && 'MergeQueue-CI' + || github.event_name == 'push' && 'Main-CI' + || 'CI' }} on: push: @@ -141,9 +146,16 @@ jobs: # --------------------------------------------------------------------------- # REQUIRED CHECKS GATE + # + # Fan-in aggregator that must turn RED when any required job fails or is + # cancelled. `if: always()` is critical: without it, this job is skipped + # when any `needs` dependency fails, and GitHub treats a skipped required + # status check as "not failed" — which let PR #151 merge through the + # merge queue despite failing e2e + orchestrator integration tests. # --------------------------------------------------------------------------- required-checks: name: Required Checks + if: always() runs-on: ubuntu-latest needs: - lint @@ -157,6 +169,12 @@ jobs: - storage-integration-test - consumer-integration-test steps: - - name: All required checks passed + - name: Fail if any required check did not succeed + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} run: | - echo "All required checks passed!" >&2 + echo "One or more required checks did not succeed:" >&2 + echo '${{ toJSON(needs) }}' >&2 + exit 1 + + - name: All required checks passed + run: echo "All required checks passed!" diff --git a/example/server/orchestrator/main.go b/example/server/orchestrator/main.go index 9d65a9b4..cbc7265c 100644 --- a/example/server/orchestrator/main.go +++ b/example/server/orchestrator/main.go @@ -606,13 +606,18 @@ func newChangeProvider(logger *zap.Logger, scope tally.Scope) (changeprovider.Ch } // newPusher creates a git-backed Pusher bound to the configured checkout -// path, remote, and target branch. Configured via PUSHER_CHECKOUT_PATH -// (required), PUSHER_REMOTE (default "origin"), and PUSHER_TARGET (default -// "main"). +// path, remote, and target branch. Configured via PUSHER_CHECKOUT_PATH, +// PUSHER_REMOTE (default "origin"), and PUSHER_TARGET (default "main"). +// +// If PUSHER_CHECKOUT_PATH is not set the orchestrator falls back to a +// no-op pusher that errors when invoked. This keeps the example server +// runnable in environments that don't exercise the merge controller +// (e.g. ping-only integration tests, local dev without a git checkout). func newPusher(logger *zap.Logger, scope tally.Scope) (pusher.Pusher, error) { checkout := os.Getenv("PUSHER_CHECKOUT_PATH") if checkout == "" { - return nil, fmt.Errorf("PUSHER_CHECKOUT_PATH environment variable is required") + logger.Warn("PUSHER_CHECKOUT_PATH not set; using no-op pusher (merge controller will fail if invoked)") + return noopPusher{}, nil } return gitpusher.NewPusher(gitpusher.Params{ CheckoutPath: checkout, @@ -622,3 +627,15 @@ func newPusher(logger *zap.Logger, scope tally.Scope) (pusher.Pusher, error) { MetricsScope: scope.SubScope("pusher"), }), nil } + +// noopPusher is a fallback Pusher used when PUSHER_CHECKOUT_PATH is not +// configured. It returns an error on every Push so the merge controller +// (which treats non-ErrConflict errors as transient and nacks the message) +// will not silently report success. It exists so the orchestrator can +// still start up — and serve Ping / accept enqueues — in environments +// that don't run the merge step. +type noopPusher struct{} + +func (noopPusher) Push(_ context.Context, _ []entity.Change) (pusher.Result, error) { + return pusher.Result{}, fmt.Errorf("pusher not configured: set PUSHER_CHECKOUT_PATH to enable pushing") +}