From 27d4dc8a35f2c550709916969b712dfd07cf299b Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 10:56:29 +0100 Subject: [PATCH 1/9] Add create-signed-pull-request composite action Wraps DataDog/commit-headless so bot workflows can create GitHub-signed commits --- .../create-signed-pull-request/action.yml | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 .github/actions/create-signed-pull-request/action.yml diff --git a/.github/actions/create-signed-pull-request/action.yml b/.github/actions/create-signed-pull-request/action.yml new file mode 100644 index 000000000000..6ec8485a08f0 --- /dev/null +++ b/.github/actions/create-signed-pull-request/action.yml @@ -0,0 +1,143 @@ +name: 'Create Signed Pull Request' +description: 'Commits the working tree as a GitHub-signed commit, and opens or updates a pull request' + +# Replacement for peter-evans/create-pull-request, which commits over git and so cannot produce +# signed commits. This action stages the working tree and hands it to DataDog/commit-headless, +# which creates the commit through the GitHub API so that GitHub signs it server-side. +# See https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5580588264 + +inputs: + token: + description: 'GitHub token used to push the commit and manage the pull request. Needs contents:write and pull_requests:write.' + required: true + branch: + description: 'Head branch to create or update' + required: true + commit-message: + description: 'Message for the commit' + required: true + title: + description: 'Pull request title' + required: true + base: + description: 'Base branch for the pull request. Defaults to the repository default branch.' + required: false + body: + description: 'Pull request body' + required: false + labels: + description: 'Comma-separated labels to apply' + required: false + reviewers: + description: 'Comma-separated reviewers to request. Accepts org/team slugs.' + required: false + +outputs: + pull-request-number: + description: 'Number of the created or updated pull request' + value: ${{ steps.pr.outputs.pull-request-number }} + pull-request-url: + description: 'URL of the created or updated pull request' + value: ${{ steps.pr.outputs.pull-request-url }} + +runs: + using: "composite" + steps: + - name: Stage changes + id: prep + shell: bash + env: + # Token for calling the github API + GH_TOKEN: ${{ inputs.token }} + BRANCH: ${{ inputs.branch }} + run: | + # Record the commit we branch from before adding anything of our own. + echo "base-sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + git add -A + if git diff --cached --quiet; then + echo "No changes to commit." + echo "changes=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "changes=true" >> "$GITHUB_OUTPUT" + + # matching-refs returns an empty array rather than 404, so a missing branch is not an error. + # It matches by prefix, hence the exact whole-line comparison. Keeping the gh call out of the + # `if` means a genuine API failure still aborts the step rather than reading as "no branch". + refs=$(gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/heads/$BRANCH" --jq '.[].ref') + + if printf '%s\n' "$refs" | grep -qxF "refs/heads/$BRANCH"; then + exists=true + else + exists=false + fi + echo "Remote branch $BRANCH exists: $exists" + + # create-branch and force are mutually exclusive: the former creates the ref (and fails if + # it exists), the latter updates it (and fails if it does not). + if [ "$exists" = true ]; then + create_branch=false + force=true + else + create_branch=true + force=false + fi + + { + echo "create-branch=$create_branch" + echo "force=$force" + } >> "$GITHUB_OUTPUT" + + - name: Create signed commit + id: push + if: steps.prep.outputs.changes == 'true' + uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + with: + token: ${{ inputs.token }} + command: commit + branch: ${{ inputs.branch }} + message: ${{ inputs.commit-message }} + # The committer is already the token owner, so an empty author suppresses co-author byline + author: '' + head-sha: ${{ steps.prep.outputs.base-sha }} + create-branch: ${{ steps.prep.outputs.create-branch }} + force: ${{ steps.prep.outputs.force }} + + - name: Create or update pull request + id: pr + if: steps.prep.outputs.changes == 'true' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + BRANCH: ${{ inputs.branch }} + BASE: ${{ inputs.base }} + TITLE: ${{ inputs.title }} + BODY: ${{ inputs.body }} + LABELS: ${{ inputs.labels }} + REVIEWERS: ${{ inputs.reviewers }} + run: | + existing=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty') + + if [ -z "$existing" ]; then + args=(pr create --head "$BRANCH" --title "$TITLE" --body-file -) + if [ -n "$BASE" ]; then args+=(--base "$BASE"); fi + if [ -n "$LABELS" ]; then args+=(--label "$LABELS"); fi + if [ -n "$REVIEWERS" ]; then args+=(--reviewer "$REVIEWERS"); fi + verb=Created + else + args=(pr edit "$existing" --title "$TITLE" --body-file -) + if [ -n "$LABELS" ]; then args+=(--add-label "$LABELS"); fi + verb=Updated + fi + + # Bodies are multi-line, so pass them on stdin. --body-file - avoids handing a + # shell-built path to gh, which is a Windows executable on the Windows runners. + url=$(printf '%s' "$BODY" | gh "${args[@]}") + echo "$verb $url" + + { + echo "pull-request-number=$(gh pr view "$BRANCH" --json number --jq '.number')" + echo "pull-request-url=$url" + } >> "$GITHUB_OUTPUT" From a76eb4ead38b321b0825a771573f54e7f30bfae8 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 10:57:16 +0100 Subject: [PATCH 2/9] Sign commits in auto_bump_smoke_test_docker_images --- .github/workflows/auto_bump_smoke_test_docker_images.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto_bump_smoke_test_docker_images.yml b/.github/workflows/auto_bump_smoke_test_docker_images.yml index 443efd2cc581..41251269ace0 100644 --- a/.github/workflows/auto_bump_smoke_test_docker_images.yml +++ b/.github/workflows/auto_bump_smoke_test_docker_images.yml @@ -27,6 +27,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -51,7 +53,7 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} # The branch name is referenced in .azure-pipelines/ultimate-pipeline.yml @@ -59,7 +61,6 @@ jobs: # Keep the two in sync if this ever changes. branch: "bot/smoke-test-docker-image-bump" commit-message: "[Smoke Test Docker Image Bump]" - delete-branch: true base: master title: "[Smoke Test Docker Image Bump] Updating docker image tags " labels: "area:dependabot,area:test-apps,dependencies" From 97f074ea1081e8084850830e76cbd0a1897dc371 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 10:57:54 +0100 Subject: [PATCH 3/9] Sign commits in auto_bump_test_package_versions Also drops the milestone input: it referenced steps.rename.outputs, and there is no step with that id, so it always resolved to empty --- .github/workflows/auto_bump_test_package_versions.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/auto_bump_test_package_versions.yml b/.github/workflows/auto_bump_test_package_versions.yml index 81a781c42899..9acc10107cdc 100644 --- a/.github/workflows/auto_bump_test_package_versions.yml +++ b/.github/workflows/auto_bump_test_package_versions.yml @@ -29,6 +29,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -53,15 +55,13 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "bot/test-package-versions-bump" commit-message: "[Test Package Versions Bump]" - delete-branch: true base: master title: "[Test Package Versions Bump] Updating package versions " - milestone: "${{steps.rename.outputs.milestone}}" labels: "area:dependabot,area:test-apps,dependencies" body: | Updates the package versions for integration tests. See below for a summary of the changes. From 08207590daf33b18c94e6610b667d62d1496ba29 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 10:58:52 +0100 Subject: [PATCH 4/9] Sign commits in auto_create_version_bump_pr The base branch now has to be stated explicitly: peter-evans defaulted it to the checked-out ref (the release branch), whereas gh defaults to the repository default branch. --- .github/workflows/auto_create_version_bump_pr.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/auto_create_version_bump_pr.yml b/.github/workflows/auto_create_version_bump_pr.yml index 31cfbed2cd59..abf31590a816 100644 --- a/.github/workflows/auto_create_version_bump_pr.yml +++ b/.github/workflows/auto_create_version_bump_pr.yml @@ -46,11 +46,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ steps.select_branch.outputs.ref }} - - - name: "Configure Git Credentials" - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -77,12 +73,12 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "version-bump-${{steps.versions.outputs.full_version}}" commit-message: "[Version Bump] ${{steps.versions.outputs.full_version}}" - delete-branch: true + base: ${{ steps.select_branch.outputs.ref }} title: "[Version Bump] ${{steps.versions.outputs.full_version}}" reviewers: "DataDog/apm-dotnet" body: "${{steps.changes.outputs.release_notes}}" From 1f042fbfbc075ba7ba77036d3a53bd1c830278b5 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 10:59:28 +0100 Subject: [PATCH 5/9] Sign commits in force_manual_version_bump --- .github/workflows/force_manual_version_bump.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/force_manual_version_bump.yml b/.github/workflows/force_manual_version_bump.yml index 1751dce716ac..a3ca43b13cb5 100644 --- a/.github/workflows/force_manual_version_bump.yml +++ b/.github/workflows/force_manual_version_bump.yml @@ -35,6 +35,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -53,12 +55,11 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + uses: ./.github/actions/create-signed-pull-request with: token: ${{ steps.octo-sts.outputs.token }} branch: "version-bump-${{steps.versions.outputs.full_version}}" commit-message: "[Version Bump] ${{steps.versions.outputs.full_version}}" - delete-branch: true title: "[Version Bump] ${{steps.versions.outputs.full_version}}" reviewers: "DataDog/apm-dotnet" body: "${{steps.changes.outputs.release_notes}}" From 6d01c7b6893cd8ba49a8d1eb23d44b284c5aa046 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 11:00:07 +0100 Subject: [PATCH 6/9] Sign the commit in create_hotfix_branch The local git checkout -b is gone: commit-headless creates the remote branch itself via create-branch, using the dispatched tag as the branch point. The default GITHUB_TOKEN is enough, as the job already declares contents: write. --- .github/workflows/create_hotfix_branch.yml | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/create_hotfix_branch.yml b/.github/workflows/create_hotfix_branch.yml index fcecc7127bce..eae53877b40b 100644 --- a/.github/workflows/create_hotfix_branch.yml +++ b/.github/workflows/create_hotfix_branch.yml @@ -40,11 +40,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - - name: "Configure Git Credentials" - run: | - git config user.name "${{ github.actor }}" - git config user.email "${{ github.actor }}@users.noreply.github.com" + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -61,9 +58,16 @@ jobs: id: versions run: .\tracer\build.ps1 OutputCurrentVersionToGitHub + - name: "Stage version bump" + run: git add . + - name: "Push hotfix branch" - run: | - git checkout -b hotfix/${{ steps.versions.outputs.full_version }} - git add . - git commit -m "[Version Bump] ${{steps.versions.outputs.full_version}}" - git push origin -u hotfix/${{ steps.versions.outputs.full_version }} + uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + with: + command: commit + branch: hotfix/${{ steps.versions.outputs.full_version }} + message: "[Version Bump] ${{steps.versions.outputs.full_version}}" + # Keeps whoever dispatched the workflow as a Co-authored-by trailer. + author: "${{ github.actor }} <${{ github.actor }}@users.noreply.github.com>" + head-sha: ${{ github.sha }} + create-branch: true From c41876e5960763e960e9f635386b6877ff6d7c07 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 11:00:42 +0100 Subject: [PATCH 7/9] Sign the commit in generate_package_versions Corrects the bot email to the 41898282+ form GitHub actually attributes commits to. --- .../workflows/generate_package_versions.yml | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/generate_package_versions.yml b/.github/workflows/generate_package_versions.yml index 959085983c37..c355db15a49f 100644 --- a/.github/workflows/generate_package_versions.yml +++ b/.github/workflows/generate_package_versions.yml @@ -23,6 +23,8 @@ jobs: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false - uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.0 with: @@ -31,11 +33,21 @@ jobs: - name: "Regenerating package versions" run: .\tracer\build.ps1 GeneratePackageVersions - - name: Create commits + - name: Stage regenerated files run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - git commit -am "Updated package versions" - git push - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + git add -u + + # Regenerating and finding no changes means something is wrong, so fail instead. + if (-not (git diff --cached --name-only)) { + Write-Host "::error::GeneratePackageVersions produced no changes" + exit 1 + } + + - name: Push signed commit + uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + with: + command: commit + branch: ${{ github.ref_name }} + message: "Updated package versions" + # Suppress commit trailer + author: '' From ac7394b3cfa83b8dce1f6e7c2b62819188834ef9 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 11:01:32 +0100 Subject: [PATCH 8/9] Require signatures on every commit All workflows that create commits now push through the GitHub API, so the excluded_emails escape hatch is no longer needed. --- docs/development/GitHubActionsSecurity.md | 27 +++++++++++++++++++++++ repository.datadog.yml | 5 +---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/docs/development/GitHubActionsSecurity.md b/docs/development/GitHubActionsSecurity.md index 24728727faf4..cd3785c2dafc 100644 --- a/docs/development/GitHubActionsSecurity.md +++ b/docs/development/GitHubActionsSecurity.md @@ -47,6 +47,32 @@ Dependabot preserves the SHA-pin + `# vX.Y.Z` comment format when bumping. Revie --- +## Signed commits + +Commits reaching `master` must be signed, and `repository.datadog.yml` grants no exceptions. A workflow therefore must never create a commit with `git push` — git-created commits are unsigned and will block the PR on the `commit-signatures` merge gate. + +Instead, push through the GitHub API, which signs the commit server-side. Two options: + +- **Creating or updating a pull request** — use the local composite action: + ```yaml + - uses: ./.github/actions/create-signed-pull-request + with: + token: ${{ steps.octo-sts.outputs.token }} + branch: bot/my-branch + commit-message: "[My Bot] Update things" + title: "[My Bot] Update things" + base: master + ``` + It commits the working tree, pushes the commit signed, and creates or updates the PR. It replaces `peter-evans/create-pull-request`, which pushes over git and so cannot sign. + +- **Pushing to a branch without a pull request** — stage the changes with `git add`, then use [`DataDog/commit-headless`](https://github.com/DataDog/commit-headless) directly with `command: commit`. It builds the commit from the index, so no local `git commit` is needed. See `create_hotfix_branch.yml` and `generate_package_versions.yml`. + +Any installation token works, including the default `GITHUB_TOKEN`, so a job that only needs to push a commit does not need dd-octo-sts — just `permissions: contents: write`. Use dd-octo-sts where the existing reason for it still applies, such as needing a PR to trigger other workflows. + +Background: [Commit Headless (sign bot commits)](https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5580588264) and the [Commit Signing Enforcement FAQ](https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5105058311). + +--- + ## Reviewer checklist When reviewing a PR that touches `.github/workflows/` or `.github/actions/`: @@ -55,4 +81,5 @@ When reviewing a PR that touches `.github/workflows/` or `.github/actions/`: - [ ] Every new action is on the allowlist (or the allowlist has been updated in the same/accompanying change). - [ ] Local `./` refs (`uses: ./.github/actions/...`, `uses: ./.github/workflows/...`) are **not** version-pinned — leave them as-is. - [ ] The `# vX.Y.Z` comment reflects the actual version the SHA resolves to. +- [ ] No step creates a commit with `git push` — see [Signed commits](#signed-commits). diff --git a/repository.datadog.yml b/repository.datadog.yml index 05135e0943e8..4844e1c12154 100644 --- a/repository.datadog.yml +++ b/repository.datadog.yml @@ -8,7 +8,4 @@ kind: mergegate rules: - require: pull-request-freshness enforcement: disabled - - require: commit-signatures - excluded_emails: - - '49699333+dependabot[bot]@users.noreply.github.com' # dependabot - - '41898282+github-actions[bot]@users.noreply.github.com' # version-bump bots \ No newline at end of file + - require: commit-signatures \ No newline at end of file From 01acf9affc96292e68e7445b9b5f9aee52c708af Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Thu, 30 Jul 2026 16:45:08 +0100 Subject: [PATCH 9/9] Simplify signing by delegating to peter-evans/create-pull-request peter-evans/create-pull-request has a sign-commits input, which creates the commit through the GitHub API rather than pushing it over git. It needs a bot token to sign, and our dd-octo-sts tokens are GitHub App tokens, so it applies here. That replaces the staging, commit-headless call and gh plumbing with a single delegation, removing all the bash from this action. Keeping the wrapper means sign-commits is always set, so a workflow cannot leave it off, and the signing mechanism can still be swapped in one place. Callers are unchanged; every input they pass is still accepted. --- .../create-signed-pull-request/action.yml | 121 +++--------------- docs/development/GitHubActionsSecurity.md | 2 +- 2 files changed, 19 insertions(+), 104 deletions(-) diff --git a/.github/actions/create-signed-pull-request/action.yml b/.github/actions/create-signed-pull-request/action.yml index 6ec8485a08f0..48e9690fc6cd 100644 --- a/.github/actions/create-signed-pull-request/action.yml +++ b/.github/actions/create-signed-pull-request/action.yml @@ -1,14 +1,13 @@ name: 'Create Signed Pull Request' -description: 'Commits the working tree as a GitHub-signed commit, and opens or updates a pull request' +description: 'Creates or updates a pull request whose commit is signed by GitHub' -# Replacement for peter-evans/create-pull-request, which commits over git and so cannot produce -# signed commits. This action stages the working tree and hands it to DataDog/commit-headless, -# which creates the commit through the GitHub API so that GitHub signs it server-side. -# See https://datadoghq.atlassian.net/wiki/spaces/DEVX/pages/5580588264 +# Thin wrapper over peter-evans/create-pull-request that always sets sign-commits +# Signing needs a bot token: our dd-octo-sts tokens are GitHub App tokens, so the commit is signed +# and attributed to the app. A PAT would silently produce unsigned commits. inputs: token: - description: 'GitHub token used to push the commit and manage the pull request. Needs contents:write and pull_requests:write.' + description: 'GitHub App token used to commit and manage the pull request. Needs contents:write and pull_requests:write.' required: true branch: description: 'Head branch to create or update' @@ -20,7 +19,7 @@ inputs: description: 'Pull request title' required: true base: - description: 'Base branch for the pull request. Defaults to the repository default branch.' + description: 'Base branch for the pull request. Defaults to the branch that was checked out.' required: false body: description: 'Pull request body' @@ -29,7 +28,7 @@ inputs: description: 'Comma-separated labels to apply' required: false reviewers: - description: 'Comma-separated reviewers to request. Accepts org/team slugs.' + description: 'Comma-separated reviewers to request. GitHub usernames, not teams.' required: false outputs: @@ -43,101 +42,17 @@ outputs: runs: using: "composite" steps: - - name: Stage changes - id: prep - shell: bash - env: - # Token for calling the github API - GH_TOKEN: ${{ inputs.token }} - BRANCH: ${{ inputs.branch }} - run: | - # Record the commit we branch from before adding anything of our own. - echo "base-sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - git add -A - if git diff --cached --quiet; then - echo "No changes to commit." - echo "changes=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "changes=true" >> "$GITHUB_OUTPUT" - - # matching-refs returns an empty array rather than 404, so a missing branch is not an error. - # It matches by prefix, hence the exact whole-line comparison. Keeping the gh call out of the - # `if` means a genuine API failure still aborts the step rather than reading as "no branch". - refs=$(gh api "repos/$GITHUB_REPOSITORY/git/matching-refs/heads/$BRANCH" --jq '.[].ref') - - if printf '%s\n' "$refs" | grep -qxF "refs/heads/$BRANCH"; then - exists=true - else - exists=false - fi - echo "Remote branch $BRANCH exists: $exists" - - # create-branch and force are mutually exclusive: the former creates the ref (and fails if - # it exists), the latter updates it (and fails if it does not). - if [ "$exists" = true ]; then - create_branch=false - force=true - else - create_branch=true - force=false - fi - - { - echo "create-branch=$create_branch" - echo "force=$force" - } >> "$GITHUB_OUTPUT" - - - name: Create signed commit - id: push - if: steps.prep.outputs.changes == 'true' - uses: DataDog/commit-headless@2801f6e08acb3a69b6c4d7b0d5deef27c1a15bc7 # action/v3.3.1 + - name: Create pull request + id: pr + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ inputs.token }} - command: commit + sign-commits: true branch: ${{ inputs.branch }} - message: ${{ inputs.commit-message }} - # The committer is already the token owner, so an empty author suppresses co-author byline - author: '' - head-sha: ${{ steps.prep.outputs.base-sha }} - create-branch: ${{ steps.prep.outputs.create-branch }} - force: ${{ steps.prep.outputs.force }} - - - name: Create or update pull request - id: pr - if: steps.prep.outputs.changes == 'true' - shell: bash - env: - GH_TOKEN: ${{ inputs.token }} - BRANCH: ${{ inputs.branch }} - BASE: ${{ inputs.base }} - TITLE: ${{ inputs.title }} - BODY: ${{ inputs.body }} - LABELS: ${{ inputs.labels }} - REVIEWERS: ${{ inputs.reviewers }} - run: | - existing=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty') - - if [ -z "$existing" ]; then - args=(pr create --head "$BRANCH" --title "$TITLE" --body-file -) - if [ -n "$BASE" ]; then args+=(--base "$BASE"); fi - if [ -n "$LABELS" ]; then args+=(--label "$LABELS"); fi - if [ -n "$REVIEWERS" ]; then args+=(--reviewer "$REVIEWERS"); fi - verb=Created - else - args=(pr edit "$existing" --title "$TITLE" --body-file -) - if [ -n "$LABELS" ]; then args+=(--add-label "$LABELS"); fi - verb=Updated - fi - - # Bodies are multi-line, so pass them on stdin. --body-file - avoids handing a - # shell-built path to gh, which is a Windows executable on the Windows runners. - url=$(printf '%s' "$BODY" | gh "${args[@]}") - echo "$verb $url" - - { - echo "pull-request-number=$(gh pr view "$BRANCH" --json number --jq '.number')" - echo "pull-request-url=$url" - } >> "$GITHUB_OUTPUT" + commit-message: ${{ inputs.commit-message }} + title: ${{ inputs.title }} + base: ${{ inputs.base }} + body: ${{ inputs.body }} + labels: ${{ inputs.labels }} + reviewers: ${{ inputs.reviewers }} + delete-branch: true diff --git a/docs/development/GitHubActionsSecurity.md b/docs/development/GitHubActionsSecurity.md index cd3785c2dafc..5881f5c7a44d 100644 --- a/docs/development/GitHubActionsSecurity.md +++ b/docs/development/GitHubActionsSecurity.md @@ -63,7 +63,7 @@ Instead, push through the GitHub API, which signs the commit server-side. Two op title: "[My Bot] Update things" base: master ``` - It commits the working tree, pushes the commit signed, and creates or updates the PR. It replaces `peter-evans/create-pull-request`, which pushes over git and so cannot sign. + It wraps `peter-evans/create-pull-request` with `sign-commits: true` always set, so the commit is created through the GitHub API and signed. Use the action rather than calling `peter-evans/create-pull-request` directly — that way the signing cannot be left off by mistake. - **Pushing to a branch without a pull request** — stage the changes with `git add`, then use [`DataDog/commit-headless`](https://github.com/DataDog/commit-headless) directly with `command: commit`. It builds the commit from the index, so no local `git commit` is needed. See `create_hotfix_branch.yml` and `generate_package_versions.yml`.