Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

pact-broker-webhooks.md 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. # Pact Broker Webhooks (PactFlow → GitHub)
  2. ## Principle
  3. Configure PactFlow webhooks to trigger provider verification in GitHub Actions via a dedicated GitHub machine user, a long-lived classic Personal Access Token (PAT), and a PactFlow-stored secret. Monitor for silent webhook failures so an expired/revoked token does not quietly block deployments for days.
  4. ## Rationale
  5. ### Why webhooks matter
  6. - PactFlow's `contract_requiring_verification_published` webhook is the mechanism that notifies a provider repo (via `repository_dispatch`) that a consumer has published a contract needing verification.
  7. - Without a working webhook, `can-i-deploy` in the consumer CI **times out** (900s) and eventually fails with `There is no verified pact between <consumer-version> and the version of <provider> currently in <env>` — even though nothing is wrong in either codebase.
  8. - Webhook failures are **silent by default**: PactFlow keeps emitting requests, GitHub keeps returning `401 Unauthorized`, but nothing alerts the team until a PR is blocked.
  9. ### Why a dedicated GitHub machine user (not a personal PAT)
  10. - Personal PATs die when the person leaves the company, rotates laptops, or revokes credentials during a security review. The contract test pipeline then breaks for reasons unrelated to any code change.
  11. - A dedicated machine user (e.g., `pactflow-<org>`) is owned by the org, has only the repos it needs, and the PAT lifecycle is controlled by the security/platform team.
  12. - GitHub **billing does not count** machine users added as outside collaborators to the specific repos they need — confirm with the org owner before assuming it's free.
  13. ### Why classic PAT with `repo` scope and no expiration
  14. - PactFlow's webhook calls the GitHub REST API's `repository_dispatch` endpoint. This endpoint requires the **`repo` scope** on a classic PAT (fine-grained PATs work for many flows but have edge cases with `repository_dispatch` that are not universally supported at time of writing — verify with current GitHub docs).
  15. - Classic PATs support "No expiration" — required to avoid the silent-failure trap every 90 days. GitHub warns against this for human users; for a locked-down machine-user PAT stored in PactFlow's secret vault, the security trade-off is documented and accepted.
  16. - The alternative — rotating a PAT every 30/60/90 days — requires tooling and coordination most teams don't yet have. Long-lived + monitored + machine-user-owned is the pragmatic default.
  17. ## Pattern Examples
  18. ### Example 1: Webhook URL, Headers, and Body
  19. ```json
  20. {
  21. "description": "Notify <provider-repo> when a consumer contract requires verification",
  22. "events": [{ "name": "contract_requiring_verification_published" }],
  23. "provider": { "name": "<provider-pacticipant-name>" },
  24. "request": {
  25. "method": "POST",
  26. "url": "https://api.github.com/repos/<org>/<provider-repo>/dispatches",
  27. "headers": {
  28. "Accept": "application/vnd.github+json",
  29. "Authorization": "Bearer ${user.githubToken}",
  30. "Content-Type": "application/json",
  31. "User-Agent": "PactFlow",
  32. "X-GitHub-Api-Version": "2022-11-28"
  33. },
  34. "body": {
  35. "event_type": "contract_requiring_verification_published",
  36. "client_payload": {
  37. "pact_url": "${pactbroker.pactUrl}",
  38. "sha": "${pactbroker.providerVersionNumber}",
  39. "branch": "${pactbroker.providerVersionBranch}",
  40. "consumer_name": "${pactbroker.consumerName}",
  41. "consumer_version_number": "${pactbroker.consumerVersionNumber}",
  42. "consumer_version_tags": "${pactbroker.consumerVersionTags}",
  43. "consumer_version_branch": "${pactbroker.consumerVersionBranch}"
  44. }
  45. }
  46. }
  47. }
  48. ```
  49. **Key Points**:
  50. - `${user.githubToken}` references a PactFlow **secret** stored in `Settings → Secrets` (web UI: `/settings/secrets`). The secret holds the classic PAT — never inline the token in the webhook body.
  51. - `${pactbroker.*}` are PactFlow-injected template variables; the provider workflow reads them from `github.event.client_payload`.
  52. - Use the `contract_requiring_verification_published` event (not `contract_published`) — the former fires only when a new pact _content_ change needs verification; the latter fires on every publish, including no-op republishes.
  53. ### Example 2: Provider GitHub Actions Workflow (Triggered by Webhook)
  54. ```yaml
  55. # .github/workflows/contract-test-provider.yml
  56. name: contract-test-provider
  57. on:
  58. repository_dispatch:
  59. types: [contract_requiring_verification_published]
  60. push:
  61. branches: [main]
  62. jobs:
  63. verify:
  64. runs-on: ubuntu-latest
  65. env:
  66. PACT_BROKER_BASE_URL: ${{ secrets.PACT_BROKER_BASE_URL }}
  67. PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
  68. # Pulled from webhook client_payload when triggered by PactFlow:
  69. PACT_PAYLOAD_URL: ${{ github.event.client_payload.pact_url }}
  70. GITHUB_SHA: ${{ github.event.client_payload.sha || github.sha }}
  71. GITHUB_BRANCH: ${{ github.event.client_payload.branch || github.head_ref || github.ref_name }}
  72. steps:
  73. - uses: actions/checkout@v4
  74. with:
  75. # Check out the provider version known to the broker — this is the provider SHA PactFlow wants verified.
  76. ref: ${{ github.event.client_payload.sha || github.sha }}
  77. - uses: actions/setup-node@v4
  78. with:
  79. node-version: 20
  80. - run: npm ci
  81. - name: Run provider verification
  82. run: npm run test:pact:provider
  83. - name: Can I deploy provider?
  84. if: github.event_name == 'push'
  85. run: npm run can:i:deploy:provider
  86. ```
  87. **Key Points**:
  88. - `repository_dispatch` is the event type emitted by GitHub when the webhook's REST call hits `/repos/<org>/<repo>/dispatches`.
  89. - The `types` filter must match the webhook's `event_type` (`contract_requiring_verification_published` here).
  90. - Checking out the provider version known to the broker (`providerVersionNumber`) ensures verification runs against the exact provider commit PactFlow registered — not whatever is on main.
  91. - `PACT_PAYLOAD_URL` makes `buildVerifierOptions` verify only the triggering pact (see `pactjs-utils-provider-verifier.md` Example 1).
  92. ### Example 3: Secret Rotation Runbook
  93. **Trigger**: `can-i-deploy` in a consumer repo times out with `There is no verified pact between <consumer-version> and the version of <provider> currently in <env>` — AND the provider's `contract-test-provider` workflow shows no recent `repository_dispatch` runs.
  94. **Diagnosis**:
  95. 1. In PactFlow UI: `Settings → Webhooks → <webhook-id> → Test`. A `401 Unauthorized` from GitHub confirms the token is dead.
  96. 2. In PactFlow UI: the webhook's "Last executed at" is hours/days stale while consumer pacts are actively being published.
  97. **Rotation**:
  98. 1. Log in to GitHub as the dedicated machine user (e.g., `pactflow-<org>`). **Do not use a personal account** — the whole point of the machine user is that the token outlives any individual.
  99. 2. `Settings → Developer settings → Personal access tokens → Tokens (classic) → Generate new token (classic)`.
  100. 3. Configure the token:
  101. - Name: `pactflow-webhook-<yyyy-mm-dd>`
  102. - Expiration: **No expiration** (accepted trade-off for a locked-down machine-user token stored in PactFlow's secret vault)
  103. - Scopes: **`repo`** (full repo scope is required by `repository_dispatch`; `public_repo` alone is insufficient for private repos)
  104. 4. Copy the new token value (shown only once).
  105. 5. In PactFlow UI: `Settings → Secrets → <secret-name>` (e.g., `githubToken`). Paste the new token into the **value** field and save. The webhook does not need to be edited — it references the secret by name via `${user.<secret-name>}`.
  106. 6. Re-test the webhook: `Settings → Webhooks → <webhook-id> → Test`. Expect `HTTP/1.1 204 No Content` (GitHub's success response for `repository_dispatch`).
  107. 7. In the provider repo: watch `Actions → contract-test-provider` for the newly dispatched run. Re-run the original consumer CI to confirm `can-i-deploy` now passes.
  108. 8. Revoke the old token: in the machine user's GitHub settings, delete the previous `pactflow-webhook-*` token so a leaked copy can't be reused.
  109. **Why no expiration**: A token with a 90-day expiry rotates 4× per year. Each rotation is a silent-failure window if the runbook isn't executed proactively. With monitoring (Example 4) + a locked-down machine-user-owned PAT that is only stored in PactFlow, long-lived is safer than short-lived-but-forgotten.
  110. ### Example 4: Staleness Monitoring (Detect Silent Webhook Failures)
  111. **Goal**: Alert the team if verification results haven't been published for a pacticipant pair in the last N hours, so an expired PAT or network issue doesn't silently block `can-i-deploy` for days.
  112. Pick one of these (in increasing order of investment):
  113. **Option A — Daily sanity CI job (cheapest)**:
  114. ```yaml
  115. # .github/workflows/pact-staleness-check.yml
  116. name: pact-staleness-check
  117. on:
  118. schedule:
  119. - cron: '0 9 * * 1-5' # weekdays 09:00 UTC
  120. workflow_dispatch:
  121. jobs:
  122. check:
  123. runs-on: ubuntu-latest
  124. steps:
  125. - name: Fail if latest verification for <pair> is older than 24h
  126. env:
  127. PACT_BROKER_BASE_URL: ${{ secrets.PACT_BROKER_BASE_URL }}
  128. PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
  129. run: |
  130. # Query broker matrix for newest verification timestamp for consumer/provider pair.
  131. # Exit 1 if > 24h old; team gets an email on the failed scheduled run.
  132. ./scripts/assert-recent-verification.sh <consumer> <provider> 86400
  133. ```
  134. **Option B — PactFlow metrics endpoint**: Use the SmartBear MCP `get_metrics` / `get_team_metrics` tool (see `pact-mcp.md`) to surface verification freshness in a dashboard or Slack digest.
  135. **Option C — Webhook delivery log**: PactFlow logs every webhook execution. Ship those logs to your SIEM / observability stack and alert on sustained 4xx responses from `api.github.com`.
  136. **Key Points**:
  137. - The point is not "which option you pick" — it's that **you pick at least one**. Without monitoring, the first time you learn the webhook is dead is when a release is blocked.
  138. - Alert threshold should match your consumer-publish cadence: if consumers publish daily, alert after 24–48h of silence; if hourly, after 3–6h.
  139. - Keep the alert noise-free: page only on sustained staleness, not a single missed run.
  140. ## Key Points
  141. - **Dedicated machine user owns the PAT** — never a personal PAT. Name it `pactflow-<org>` or similar; give it outside-collaborator access only to the specific provider repos.
  142. - **Classic PAT, `repo` scope, no expiration** — required for `repository_dispatch`. The "no expiration" trade-off is accepted in exchange for machine-user ownership + PactFlow-secret storage + staleness monitoring.
  143. - **Store the PAT as a PactFlow secret** at `/settings/secrets`, reference it from the webhook via `${user.<secret-name>}`. Never inline the token.
  144. - **Monitor for silence** — at minimum, a daily scheduled CI job that asserts a recent verification timestamp exists for each critical consumer/provider pair.
  145. - **Rotation is a runbook, not an emergency** — document it (see Example 3), keep it in the repo, and do a practice rotation once a year so it stays fresh.
  146. - **Symptom to remember**: "consumer `can-i-deploy` timeout after 900s with `There is no verified pact...`" + "provider's `contract-test-provider` workflow has no recent runs" = expired/revoked PAT. Start with Example 3.
  147. ## Related Fragments
  148. - `pactjs-utils-provider-verifier.md` — how `PACT_PAYLOAD_URL` from the webhook's `client_payload.pact_url` is consumed by `buildVerifierOptions`
  149. - `pact-consumer-framework-setup.md` — consumer CI flow that issues `can-i-deploy` and silently times out when the webhook is dead
  150. - `pact-mcp.md` — SmartBear MCP tools (`Matrix`, `Metrics - All`) useful for staleness monitoring dashboards
  151. - `contract-testing.md` — foundational CDC patterns and resilience coverage
  152. ## Anti-Patterns
  153. ### Wrong: Using a human's personal PAT
  154. ```
  155. # ❌ PactFlow secret githubToken stores the lead engineer's personal classic PAT
  156. # When they leave / rotate / revoke → all provider verifications stop silently
  157. ```
  158. ### Right: Dedicated machine user owns the PAT
  159. ```
  160. # ✅ Machine user `pactflow-<org>` generates the PAT; secret is owned by the org
  161. # PAT lifecycle is decoupled from any individual's employment or laptop state
  162. ```
  163. ### Wrong: No staleness monitoring
  164. ```
  165. # ❌ No scheduled check for verification recency
  166. # First signal that the webhook is dead: a blocked release PR, several days later
  167. ```
  168. ### Right: Daily scheduled sanity check
  169. ```
  170. # ✅ Scheduled workflow fails if latest verification > 24h old
  171. # Team gets email alert on failed scheduled run → rotate PAT before anyone is blocked
  172. ```
  173. ### Wrong: Short-expiration PAT with no rotation tooling
  174. ```
  175. # ❌ 90-day expiry PAT, no calendar reminder, no runbook
  176. # Breaks every 90 days for a day or two until someone notices
  177. ```
  178. ### Right: No-expiration PAT on machine user + monitoring + documented runbook
  179. ```
  180. # ✅ Long-lived PAT, scoped narrowly, stored in PactFlow, monitored for staleness
  181. # Rotation is intentional (security review, suspected leak) not calendar-driven
  182. ```
  183. _Source: PactFlow webhook documentation, GitHub `repository_dispatch` REST API, seon-mcp-server / seon-admin-panel production incident April 2026_