From 6d2a491d7c86028c3dfe5a722b0a5c7685dca955 Mon Sep 17 00:00:00 2001 From: urismiley Date: Thu, 16 Apr 2026 15:28:53 -0400 Subject: [PATCH 01/18] Add GitHub Action to trigger internal ADO CI on /test comment --- .github/workflows/internal-ci-trigger.yml | 153 ++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 .github/workflows/internal-ci-trigger.yml diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml new file mode 100644 index 000000000..831ea5fad --- /dev/null +++ b/.github/workflows/internal-ci-trigger.yml @@ -0,0 +1,153 @@ +# MVP: Trigger internal ADO pipeline from GitHub PR comments. +# Production version will add: Microsoft org membership check, private repo +# ref push, CredScan redaction, and fork PR support. +name: Internal CI Trigger +run-name: "Internal CI for PR #${{ github.event.issue.number }}" + +on: + issue_comment: + types: [created] + +jobs: + trigger-internal-ci: + # Only run on PR comments (not issue comments) that start with /test + if: >- + github.event.issue.pull_request + && startsWith(github.event.comment.body, '/test') + runs-on: ubuntu-latest + permissions: + statuses: write + pull-requests: read + steps: + - name: Check authorization + id: auth + env: + GH_TOKEN: ${{ github.token }} + run: | + COMMENTER="${{ github.event.comment.user.login }}" + + # Check if the commenter has write access to the repo + PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${COMMENTER}/permission" \ + --jq '.permission') + + if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" ]]; then + echo "::error::User ${COMMENTER} does not have write access (permission: ${PERMISSION})" + exit 1 + fi + + echo "authorized=true" >> "$GITHUB_OUTPUT" + echo "User ${COMMENTER} authorized (permission: ${PERMISSION})" + + - name: React to comment + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='eyes' --silent + + - name: Get PR details and pinned SHA + id: pr + env: + GH_TOKEN: ${{ github.token }} + run: | + PR_NUMBER="${{ github.event.issue.number }}" + + PR_DATA=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUMBER}" \ + --jq '{sha: .head.sha, branch: .head.ref, head_repo: .head.repo.full_name, base_repo: .base.repo.full_name}') + + COMMIT_SHA=$(echo "$PR_DATA" | jq -r '.sha') + SOURCE_BRANCH=$(echo "$PR_DATA" | jq -r '.branch') + HEAD_REPO=$(echo "$PR_DATA" | jq -r '.head_repo') + BASE_REPO=$(echo "$PR_DATA" | jq -r '.base_repo') + + # MVP: reject fork PRs — they require additional handling + if [[ "$HEAD_REPO" != "$BASE_REPO" ]]; then + echo "::error::Fork PRs are not supported yet (head: ${HEAD_REPO}, base: ${BASE_REPO})" + exit 1 + fi + + echo "commit_sha=${COMMIT_SHA}" >> "$GITHUB_OUTPUT" + echo "source_branch=${SOURCE_BRANCH}" >> "$GITHUB_OUTPUT" + echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" + + echo "PR #${PR_NUMBER} at SHA ${COMMIT_SHA} (branch: ${SOURCE_BRANCH})" + + - name: Set pending commit status + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api "repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.commit_sha }}" \ + -f state='pending' \ + -f description='Internal CI triggered, waiting for results...' \ + -f context='ms-internal-ci/oss-tests' \ + --silent + + - name: POST webhook to ADO + env: + MS_WEBHOOK_SECRET: ${{ secrets.MS_WEBHOOK_SECRET }} + run: | + PR_NUMBER="${{ steps.pr.outputs.pr_number }}" + COMMIT_SHA="${{ steps.pr.outputs.commit_sha }}" + SOURCE_BRANCH="${{ steps.pr.outputs.source_branch }}" + + # Build payload matching native GitHub PR event format + # (matches what the ADO webhook service connection expects) + PAYLOAD=$(jq -n \ + --arg action "opened" \ + --arg pr_number "$PR_NUMBER" \ + --arg sha "$COMMIT_SHA" \ + --arg branch "$SOURCE_BRANCH" \ + --arg repo "${{ github.repository }}" \ + --arg author "${{ github.event.comment.user.login }}" \ + '{ + action: $action, + number: ($pr_number | tonumber), + repository: { + full_name: $repo + }, + pull_request: { + html_url: "https://github.com/\($repo)/pull/\($pr_number)", + head: { + sha: $sha, + ref: $branch, + repo: { + full_name: $repo + } + }, + base: { + ref: "main" + }, + user: { + login: $author + } + } + }') + + # Compute HMAC-SHA256 signature + SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$MS_WEBHOOK_SECRET" -binary | xxd -p -c 256) + + # POST to ADO incoming webhook endpoint + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + -H "X-GitHub-ADO-Signature: sha256=$SIGNATURE" \ + -d "$PAYLOAD" \ + "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") + + echo "ADO webhook response: HTTP ${HTTP_STATUS}" + + if [[ "$HTTP_STATUS" -lt 200 || "$HTTP_STATUS" -ge 300 ]]; then + echo "::error::ADO webhook POST failed with HTTP ${HTTP_STATUS}" + + # Set error status so the PR doesn't stay pending forever + gh api "repos/${{ github.repository }}/statuses/${COMMIT_SHA}" \ + -f state='error' \ + -f description='Failed to trigger internal CI pipeline' \ + -f context='ms-internal-ci/oss-tests' \ + --silent + exit 1 + fi + + echo "Webhook triggered successfully" + env: + GH_TOKEN: ${{ github.token }} From 2686d07abb1616c46b4f4fea4da4fcbdf66a58f3 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 18:34:06 -0400 Subject: [PATCH 02/18] Fix duplicate env keys in internal-ci-trigger workflow --- .github/workflows/internal-ci-trigger.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 831ea5fad..e2c514cdd 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -85,6 +85,7 @@ jobs: - name: POST webhook to ADO env: MS_WEBHOOK_SECRET: ${{ secrets.MS_WEBHOOK_SECRET }} + GH_TOKEN: ${{ github.token }} run: | PR_NUMBER="${{ steps.pr.outputs.pr_number }}" COMMIT_SHA="${{ steps.pr.outputs.commit_sha }}" @@ -149,5 +150,3 @@ jobs: fi echo "Webhook triggered successfully" - env: - GH_TOKEN: ${{ github.token }} From 381f48df78b3f2e8263279501c9d6679bd0f1b54 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 18:40:45 -0400 Subject: [PATCH 03/18] Drop reaction step that requires issues:write permission --- .github/workflows/internal-ci-trigger.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index e2c514cdd..3de3d07da 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -38,13 +38,6 @@ jobs: echo "authorized=true" >> "$GITHUB_OUTPUT" echo "User ${COMMENTER} authorized (permission: ${PERMISSION})" - - name: React to comment - env: - GH_TOKEN: ${{ github.token }} - run: | - gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ - -f content='eyes' --silent - - name: Get PR details and pinned SHA id: pr env: From e1cd5bba3082e2aefd019b1e878d38f7e608a36e Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 19:00:52 -0400 Subject: [PATCH 04/18] Restore reaction step with issues:write permission --- .github/workflows/internal-ci-trigger.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 3de3d07da..9c8b7611b 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -18,6 +18,7 @@ jobs: permissions: statuses: write pull-requests: read + issues: write steps: - name: Check authorization id: auth @@ -38,6 +39,13 @@ jobs: echo "authorized=true" >> "$GITHUB_OUTPUT" echo "User ${COMMENTER} authorized (permission: ${PERMISSION})" + - name: React to comment + env: + GH_TOKEN: ${{ github.token }} + run: | + gh api "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='eyes' --silent + - name: Get PR details and pinned SHA id: pr env: From f92509f8786ce07b677d46ad2064c460bcbd3a74 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 19:02:23 -0400 Subject: [PATCH 05/18] Use write-all permissions to debug reaction step 403 --- .github/workflows/internal-ci-trigger.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 9c8b7611b..09d2c465b 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -15,10 +15,7 @@ jobs: github.event.issue.pull_request && startsWith(github.event.comment.body, '/test') runs-on: ubuntu-latest - permissions: - statuses: write - pull-requests: read - issues: write + permissions: write-all steps: - name: Check authorization id: auth From f531aabe6e31473a95f2b347d43d8966780ba7ea Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 19:04:26 -0400 Subject: [PATCH 06/18] Strip sha256= prefix from ADO webhook signature header --- .github/workflows/internal-ci-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 09d2c465b..390131834 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -129,7 +129,7 @@ jobs: HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ - -H "X-GitHub-ADO-Signature: sha256=$SIGNATURE" \ + -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") From 45877e43d446d0a5a9b66231e94684c36ee4b8d5 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 19:32:22 -0400 Subject: [PATCH 07/18] Capture ADO webhook response body for debugging --- .github/workflows/internal-ci-trigger.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 390131834..8d7a92f9b 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -126,14 +126,18 @@ jobs: SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$MS_WEBHOOK_SECRET" -binary | xxd -p -c 256) # POST to ADO incoming webhook endpoint - HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") + HTTP_STATUS=$(echo "$RESPONSE" | grep -oP 'HTTP_STATUS:\K\d+') + BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS:/d') + echo "ADO webhook response: HTTP ${HTTP_STATUS}" + echo "Response body: ${BODY}" if [[ "$HTTP_STATUS" -lt 200 || "$HTTP_STATUS" -ge 300 ]]; then echo "::error::ADO webhook POST failed with HTTP ${HTTP_STATUS}" From 6835791c58b70bb4e7dc94b6148607c6c81ec60e Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 19:35:54 -0400 Subject: [PATCH 08/18] Use SHA1 for ADO webhook HMAC signature (matches ADO verification) --- .github/workflows/internal-ci-trigger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 8d7a92f9b..20f6349cb 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -122,8 +122,8 @@ jobs: } }') - # Compute HMAC-SHA256 signature - SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$MS_WEBHOOK_SECRET" -binary | xxd -p -c 256) + # Compute HMAC-SHA1 signature (ADO incoming webhook verifies with SHA1) + SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha1 -hmac "$MS_WEBHOOK_SECRET" -binary | xxd -p -c 256) # POST to ADO incoming webhook endpoint RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \ From 0129e679990cbe11ac3e198fd4453a013a44bccf Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 21 Apr 2026 22:25:49 -0400 Subject: [PATCH 09/18] Pass triggerer (commenter) as separate field in webhook payload --- .github/workflows/internal-ci-trigger.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 20f6349cb..cb382b4a4 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -97,10 +97,11 @@ jobs: --arg sha "$COMMIT_SHA" \ --arg branch "$SOURCE_BRANCH" \ --arg repo "${{ github.repository }}" \ - --arg author "${{ github.event.comment.user.login }}" \ + --arg triggerer "${{ github.event.comment.user.login }}" \ '{ action: $action, number: ($pr_number | tonumber), + triggerer: $triggerer, repository: { full_name: $repo }, @@ -117,7 +118,7 @@ jobs: ref: "main" }, user: { - login: $author + login: $triggerer } } }') From 1aa0d039247a44e5ca8ebfbeeeef2074c09e4cd9 Mon Sep 17 00:00:00 2001 From: urismiley Date: Wed, 22 Apr 2026 00:27:29 -0400 Subject: [PATCH 10/18] Skip internal CI when PR only changes docs/CI files (with /test force override) --- .github/workflows/internal-ci-trigger.yml | 77 +++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index cb382b4a4..70d3dc1cb 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -70,7 +70,83 @@ jobs: echo "PR #${PR_NUMBER} at SHA ${COMMIT_SHA} (branch: ${SOURCE_BRANCH})" + - name: Path filter — skip if only irrelevant files changed + id: pathfilter + env: + GH_TOKEN: ${{ github.token }} + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + PR_NUMBER="${{ steps.pr.outputs.pr_number }}" + + # Allow override: `/test force` bypasses the path filter + if echo "$COMMENT_BODY" | grep -qiE '^/test[[:space:]]+force\b'; then + echo "Path filter bypassed via '/test force'" + echo "skip=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Files matching any of these patterns are considered irrelevant to internal CI. + # If the PR touches *only* these, we skip the expensive run. + IGNORE_PATTERNS=( + '*.md' + '**/*.md' + 'LICENSE' + 'NOTICE' + 'CODEOWNERS' + 'MAINTAINERS.md' + '.gitignore' + '.gitattributes' + 'docs/**' + '.github/**' + 'rfcs/**' + 'licenses/**' + ) + + # Get list of changed files (paginated, up to 3000) + mapfile -t FILES < <(gh api --paginate \ + "repos/${{ github.repository }}/pulls/${PR_NUMBER}/files" \ + --jq '.[].filename') + + if [[ ${#FILES[@]} -eq 0 ]]; then + echo "::warning::No files reported changed; running CI anyway" + echo "skip=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "Changed files (${#FILES[@]}):" + printf ' %s\n' "${FILES[@]}" + + shopt -s globstar extglob nullglob + RELEVANT=0 + for f in "${FILES[@]}"; do + matched=0 + for pat in "${IGNORE_PATTERNS[@]}"; do + # shellcheck disable=SC2053 + if [[ "$f" == $pat ]]; then + matched=1 + break + fi + done + if [[ $matched -eq 0 ]]; then + echo "Relevant file: $f" + RELEVANT=1 + break + fi + done + + if [[ $RELEVANT -eq 0 ]]; then + echo "All changed files match ignore patterns — skipping internal CI" + echo "skip=true" >> "$GITHUB_OUTPUT" + + gh api "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \ + -f body="⏭️ Internal CI skipped: PR only touches docs/CI/license files. Use \`/test force\` to override." \ + --silent + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + - name: Set pending commit status + if: steps.pathfilter.outputs.skip != 'true' env: GH_TOKEN: ${{ github.token }} run: | @@ -81,6 +157,7 @@ jobs: --silent - name: POST webhook to ADO + if: steps.pathfilter.outputs.skip != 'true' env: MS_WEBHOOK_SECRET: ${{ secrets.MS_WEBHOOK_SECRET }} GH_TOKEN: ${{ github.token }} From 0b3b1896b1037066968ed83a4f9b97a272f60537 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 5 May 2026 18:22:46 -0400 Subject: [PATCH 11/18] Align internal CI status context Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/internal-ci-trigger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 70d3dc1cb..90242c7ec 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -153,7 +153,7 @@ jobs: gh api "repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.commit_sha }}" \ -f state='pending' \ -f description='Internal CI triggered, waiting for results...' \ - -f context='ms-internal-ci/oss-tests' \ + -f context='ms-internal-ci/oss-jstests' \ --silent - name: POST webhook to ADO @@ -224,7 +224,7 @@ jobs: gh api "repos/${{ github.repository }}/statuses/${COMMIT_SHA}" \ -f state='error' \ -f description='Failed to trigger internal CI pipeline' \ - -f context='ms-internal-ci/oss-tests' \ + -f context='ms-internal-ci/oss-jstests' \ --silent exit 1 fi From 641c552b1d8f766dd1ac6056498aa32fe34c1d0c Mon Sep 17 00:00:00 2001 From: urismiley Date: Mon, 11 May 2026 23:09:11 -0400 Subject: [PATCH 12/18] Use external CI webhook service connection URL Signed-off-by: urismiley --- .github/workflows/internal-ci-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 90242c7ec..5d405b077 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -209,7 +209,7 @@ jobs: -H "Content-Type: application/json" \ -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ - "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") + "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/documentdb-oss-external-ci-webhook?api-version=6.0-preview") HTTP_STATUS=$(echo "$RESPONSE" | grep -oP 'HTTP_STATUS:\K\d+') BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS:/d') From 0ac056f2d94d86b0ecce04d3fc169c1369b664e7 Mon Sep 17 00:00:00 2001 From: urismiley Date: Mon, 11 May 2026 23:32:00 -0400 Subject: [PATCH 13/18] Use configured incoming webhook name Signed-off-by: urismiley --- .github/workflows/internal-ci-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 5d405b077..90242c7ec 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -209,7 +209,7 @@ jobs: -H "Content-Type: application/json" \ -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ - "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/documentdb-oss-external-ci-webhook?api-version=6.0-preview") + "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") HTTP_STATUS=$(echo "$RESPONSE" | grep -oP 'HTTP_STATUS:\K\d+') BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS:/d') From 628743b565342c194212b73681317b8a5e1d7915 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 12 May 2026 01:42:10 -0400 Subject: [PATCH 14/18] Use unique ADO webhook endpoint Signed-off-by: urismiley (cherry picked from commit d02bef717a276b852db5c020183e969bf41ea575) --- .github/workflows/internal-ci-trigger.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 90242c7ec..5d405b077 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -209,7 +209,7 @@ jobs: -H "Content-Type: application/json" \ -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ - "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/WebHook?api-version=6.0-preview") + "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/documentdb-oss-external-ci-webhook?api-version=6.0-preview") HTTP_STATUS=$(echo "$RESPONSE" | grep -oP 'HTTP_STATUS:\K\d+') BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS:/d') From 8a23b80148fba15de1f3a848f8b449a112a3e659 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 19 May 2026 12:55:26 -0400 Subject: [PATCH 15/18] Harden internal CI trigger workflow --- .github/workflows/internal-ci-trigger.yml | 55 ++++++++++++++++++----- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index 5d405b077..cc1098858 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -1,6 +1,6 @@ # MVP: Trigger internal ADO pipeline from GitHub PR comments. -# Production version will add: Microsoft org membership check, private repo -# ref push, CredScan redaction, and fork PR support. +# Production version will add: private repo ref push, CredScan redaction, +# and fork PR support. name: Internal CI Trigger run-name: "Internal CI for PR #${{ github.event.issue.number }}" @@ -10,17 +10,35 @@ on: jobs: trigger-internal-ci: - # Only run on PR comments (not issue comments) that start with /test + # Only run on PR comments (not issue comments) that are /test commands. if: >- github.event.issue.pull_request - && startsWith(github.event.comment.body, '/test') + && ( + github.event.comment.body == '/test' + || startsWith(github.event.comment.body, '/test ') + ) runs-on: ubuntu-latest - permissions: write-all + permissions: + contents: read + issues: write + pull-requests: read + statuses: write steps: + - name: Validate test command + env: + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + if [[ ! "$COMMENT_BODY" =~ ^/test([[:space:]]+force)?[[:space:]]*$ ]]; then + echo "::error::Unsupported internal CI command. Use /test or /test force." + exit 1 + fi + - name: Check authorization id: auth env: GH_TOKEN: ${{ github.token }} + MICROSOFT_GITHUB_ORG: microsoft + MS_ORG_READ_TOKEN: ${{ secrets.MS_ORG_READ_TOKEN }} run: | COMMENTER="${{ github.event.comment.user.login }}" @@ -33,8 +51,18 @@ jobs: exit 1 fi + if [[ -z "${MS_ORG_READ_TOKEN:-}" ]]; then + echo "::error::MS_ORG_READ_TOKEN secret is required to verify Microsoft org membership" + exit 1 + fi + + if ! GH_TOKEN="$MS_ORG_READ_TOKEN" gh api "orgs/${MICROSOFT_GITHUB_ORG}/members/${COMMENTER}" --silent; then + echo "::error::User ${COMMENTER} is not a member of ${MICROSOFT_GITHUB_ORG}, or membership could not be verified" + exit 1 + fi + echo "authorized=true" >> "$GITHUB_OUTPUT" - echo "User ${COMMENTER} authorized (permission: ${PERMISSION})" + echo "User ${COMMENTER} authorized (permission: ${PERMISSION}, org: ${MICROSOFT_GITHUB_ORG})" - name: React to comment env: @@ -166,6 +194,11 @@ jobs: COMMIT_SHA="${{ steps.pr.outputs.commit_sha }}" SOURCE_BRANCH="${{ steps.pr.outputs.source_branch }}" + if [[ -z "${MS_WEBHOOK_SECRET:-}" ]]; then + echo "::error::MS_WEBHOOK_SECRET secret is required to trigger ADO" + exit 1 + fi + # Build payload matching native GitHub PR event format # (matches what the ADO webhook service connection expects) PAYLOAD=$(jq -n \ @@ -200,22 +233,20 @@ jobs: } }') - # Compute HMAC-SHA1 signature (ADO incoming webhook verifies with SHA1) + # Compute the raw HMAC-SHA1 hex signature expected by the ADO + # documentdb-oss-external-ci-webhook service connection. Unsigned + # and bad-signature requests must fail before queuing ADO pipeline 56298. SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha1 -hmac "$MS_WEBHOOK_SECRET" -binary | xxd -p -c 256) # POST to ADO incoming webhook endpoint - RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}" \ + HTTP_STATUS=$(curl -sS -o /dev/null -w "%{http_code}" \ -X POST \ -H "Content-Type: application/json" \ -H "X-GitHub-ADO-Signature: $SIGNATURE" \ -d "$PAYLOAD" \ "https://dev.azure.com/msdata/_apis/public/distributedtask/webhooks/documentdb-oss-external-ci-webhook?api-version=6.0-preview") - HTTP_STATUS=$(echo "$RESPONSE" | grep -oP 'HTTP_STATUS:\K\d+') - BODY=$(echo "$RESPONSE" | sed '/HTTP_STATUS:/d') - echo "ADO webhook response: HTTP ${HTTP_STATUS}" - echo "Response body: ${BODY}" if [[ "$HTTP_STATUS" -lt 200 || "$HTTP_STATUS" -ge 300 ]]; then echo "::error::ADO webhook POST failed with HTTP ${HTTP_STATUS}" From 736e8ed6fd21abb8f8405ca6cab8ddf23e796806 Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 19 May 2026 13:20:30 -0400 Subject: [PATCH 16/18] Make internal CI comment reaction non-blocking --- .github/workflows/internal-ci-trigger.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index cc1098858..f2e450fcb 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -65,6 +65,7 @@ jobs: echo "User ${COMMENTER} authorized (permission: ${PERMISSION}, org: ${MICROSOFT_GITHUB_ORG})" - name: React to comment + continue-on-error: true env: GH_TOKEN: ${{ github.token }} run: | From db67bc199524e8e06afaaeb8ad105e68b09e3bca Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 19 May 2026 14:16:34 -0400 Subject: [PATCH 17/18] Send comment-shaped internal CI webhooks --- .github/workflows/internal-ci-trigger.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/internal-ci-trigger.yml b/.github/workflows/internal-ci-trigger.yml index f2e450fcb..9225a1a80 100644 --- a/.github/workflows/internal-ci-trigger.yml +++ b/.github/workflows/internal-ci-trigger.yml @@ -200,19 +200,36 @@ jobs: exit 1 fi - # Build payload matching native GitHub PR event format - # (matches what the ADO webhook service connection expects) + # Build a comment-shaped payload so each /test comment is a distinct + # webhook event while still carrying the pinned PR SHA for ADO. PAYLOAD=$(jq -n \ - --arg action "opened" \ + --arg action "created" \ --arg pr_number "$PR_NUMBER" \ --arg sha "$COMMIT_SHA" \ --arg branch "$SOURCE_BRANCH" \ --arg repo "${{ github.repository }}" \ --arg triggerer "${{ github.event.comment.user.login }}" \ + --arg comment_id "${{ github.event.comment.id }}" \ + --arg comment_url "${{ github.event.comment.html_url }}" \ + --arg comment_created_at "${{ github.event.comment.created_at }}" \ '{ action: $action, number: ($pr_number | tonumber), triggerer: $triggerer, + comment: { + id: ($comment_id | tonumber), + html_url: $comment_url, + created_at: $comment_created_at, + user: { + login: $triggerer + } + }, + issue: { + number: ($pr_number | tonumber), + pull_request: { + url: "https://api.github.com/repos/\($repo)/pulls/\($pr_number)" + } + }, repository: { full_name: $repo }, From 05eeaaeeeaab3a593c2d6b80c1cdec9d767e3aca Mon Sep 17 00:00:00 2001 From: urismiley Date: Tue, 19 May 2026 21:08:41 -0400 Subject: [PATCH 18/18] Create external CI demo PR