Skip to content

Commit a6af4d2

Browse files
fix(ci): sign auto-generated docs commits (#5154)
The `update-docs` workflow creates docs commits with a hand-set bot identity (`github-aws-runners-pr|bot`) while pushing with `GITHUB_TOKEN`. The resulting commits are unsigned, show as unverified, and don't resolve to any GitHub account (see [dd5f00c](dd5f00c) on #5152 — `verification.reason: unsigned`, no linked author). Key changes: - The terraform-docs step no longer commits or pushes (`git-push: false`); a new step creates the commit through the GraphQL `createCommitOnBranch` mutation instead. Commits created via the API are signed by GitHub and show as **Verified**, attributed to `github-actions[bot]`. This avoids adding a new third-party action dependency. - The main-branch PR path enables `sign-commits: true` on `peter-evans/create-pull-request`, which also creates commits via the API for the same effect. - The fork path is unchanged, since forks push to their own repositories. Notes: - Pushing via `createCommitOnBranch` with `GITHUB_TOKEN` does not trigger new workflow runs, matching the previous `git push` behavior (no recursion). - The `mkdocs gh-deploy` commits to `gh-pages` remain unsigned; mkdocs commits locally via git, so it can't use the API. Its identity (`github-actions[bot]`) at least matches the token used. Validated with actionlint and zizmor (no findings on this workflow). ## Testing Tested end-to-end on a throwaway branch ([`gc/test/verified-docs-commits`](https://github.com/github-aws-runners/terraform-aws-github-runner/tree/gc/test/verified-docs-commits)) containing this workflow plus a deliberately stale doc (a tweaked variable description in `modules/webhook/variables.tf` without regenerating the READMEs): - **No-change path**: the run on this PR's branch exits cleanly with `No documentation changes to commit.` ([run 27298488008](https://github.com/github-aws-runners/terraform-aws-github-runner/actions/runs/27298488008)) - **Commit path**: the run on the test branch regenerated 4 READMEs and created the commit via the API ([run 27303747943](https://github.com/github-aws-runners/terraform-aws-github-runner/actions/runs/27303747943)) — see [5116de7](5116de7), which shows as **Verified**: ```console $ gh api repos/github-aws-runners/terraform-aws-github-runner/commits/5116de7ddf1a45c52ec2b5ce2b702d7f0dbcf5d2 \ --jq '{author: .commit.author, committer_login: .committer.login, verified: .commit.verification.verified, reason: .commit.verification.reason}' { "author": { "date": "2026-06-10T20:19:54Z", "email": "41898282+github-actions[bot]@users.noreply.github.com", "name": "github-actions[bot]" }, "committer_login": "web-flow", "verified": true, "reason": "valid" } ``` Compare with the unverified commit that motivated this PR: `dd5f00c` has `"verified": false, "reason": "unsigned"` and no resolvable author account. Testing also surfaced two fixes now included here: terraform-docs stages the files it updates, so the change detection diffs against `HEAD`; and the GraphQL payload is passed to `jq` via a temp file (`--slurpfile`) because embedding file contents in a CLI argument exceeded the kernel's per-argument size limit. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent e029d95 commit a6af4d2

4 files changed

Lines changed: 44 additions & 6 deletions

File tree

.github/workflows/update-docs.yml

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,50 @@ jobs:
3232
token: ${{ secrets.GITHUB_TOKEN }}
3333
persist-credentials: true
3434

35-
# use an app to ensure CI is triggered
3635
- name: Generate TF docs
3736
if: github.repository_owner == 'github-aws-runners'
3837
uses: terraform-docs/gh-actions@6de6da0cefcc6b4b7a5cbea4d79d97060733093c # v1.4.1
3938
with:
4039
find-dir: .
41-
git-commit-message: "docs: auto update terraform docs"
42-
git-push: ${{ github.ref != 'refs/heads/main' || github.repository_owner != 'github-aws-runners' }}
43-
git-push-user-name: github-aws-runners-pr|bot
44-
git-push-user-email: "github-aws-runners-pr[bot]@users.noreply.github.com"
40+
git-push: false
41+
42+
# commit via the GitHub API so commits are signed by GitHub and show as verified
43+
- name: Commit and push docs changes (branches only)
44+
if: github.ref != 'refs/heads/main' && github.repository_owner == 'github-aws-runners'
45+
env:
46+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47+
COMMIT_MESSAGE: "docs: auto update terraform docs"
48+
run: |
49+
set -o pipefail
50+
# diff against HEAD because terraform-docs stages the files it updates
51+
if git diff --quiet HEAD; then
52+
echo "No documentation changes to commit."
53+
exit 0
54+
fi
55+
echo "Committing documentation changes:"
56+
git diff --name-only HEAD
57+
# pass file contents through a temp file to avoid the kernel's argument size limit
58+
additions=$(mktemp)
59+
git diff --name-only HEAD | while IFS= read -r file; do
60+
jq -n --arg path "$file" --rawfile contents <(base64 -w0 "$file") '{path: $path, contents: $contents}'
61+
done | jq -s '.' > "$additions"
62+
jq -n \
63+
--arg repository "$GITHUB_REPOSITORY" \
64+
--arg branch "$GITHUB_REF_NAME" \
65+
--arg expectedHeadOid "$(git rev-parse HEAD)" \
66+
--arg message "$COMMIT_MESSAGE" \
67+
--slurpfile additions "$additions" \
68+
'{
69+
query: "mutation ($input: CreateCommitOnBranchInput!) { createCommitOnBranch(input: $input) { commit { oid } } }",
70+
variables: {
71+
input: {
72+
branch: { repositoryNameWithOwner: $repository, branchName: $branch },
73+
expectedHeadOid: $expectedHeadOid,
74+
message: { headline: $message },
75+
fileChanges: { additions: $additions[0] }
76+
}
77+
}
78+
}' | gh api graphql --input -
4579
4680
- name: Generate TF docs (forks)
4781
if: github.repository_owner != 'github-aws-runners'
@@ -57,6 +91,7 @@ jobs:
5791
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
5892
with:
5993
token: ${{ secrets.GITHUB_TOKEN }}
94+
sign-commits: true
6095
commit-message: "docs: auto update terraform docs"
6196
title: "docs: Update Terraform docs"
6297
branch: update-docs

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ Join our discord community via [this invite link](https://discord.gg/bxgXW8jJGh)
138138
| <a name="input_ghes_ssl_verify"></a> [ghes\_ssl\_verify](#input\_ghes\_ssl\_verify) | GitHub Enterprise SSL verification. Set to 'false' when custom certificate (chains) is used for GitHub Enterprise Server (insecure). | `bool` | `true` | no |
139139
| <a name="input_ghes_url"></a> [ghes\_url](#input\_ghes\_url) | GitHub Enterprise Server URL. Example: https://github.internal.co - DO NOT SET IF USING PUBLIC GITHUB. However if you are using GitHub Enterprise Cloud with data-residency (ghe.com), set the endpoint here. Example - https://companyname.ghe.com | `string` | `null` | no |
140140
| <a name="input_github_app"></a> [github\_app](#input\_github\_app) | GitHub app parameters, see your github app.<br/> You can optionally create the SSM parameters yourself and provide the ARN and name here, through the `*_ssm` attributes.<br/> If you chose to provide the configuration values directly here,<br/> please ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`).<br/> Note: the provided SSM parameters arn and name have a precedence over the actual value (i.e `key_base64_ssm` has a precedence over `key_base64` etc). | <pre>object({<br/> key_base64 = optional(string)<br/> key_base64_ssm = optional(object({<br/> arn = string<br/> name = string<br/> }))<br/> id = optional(string)<br/> id_ssm = optional(object({<br/> arn = string<br/> name = string<br/> }))<br/> webhook_secret = optional(string)<br/> webhook_secret_ssm = optional(object({<br/> arn = string<br/> name = string<br/> }))<br/> })</pre> | n/a | yes |
141+
| <a name="input_iam_overrides"></a> [iam\_overrides](#input\_iam\_overrides) | This map provides the possibility to override some IAM defaults. Note that when using this variable, you are responsible for ensuring the role has necessary permissions to access required resources. `override_instance_profile`: When set to true, uses the instance profile name specified in `instance_profile_name` instead of creating a new instance profile. `override_runner_role`: When set to true, uses the role ARN specified in `runner_role_arn` instead of creating a new IAM role. | <pre>object({<br/> override_instance_profile = optional(bool, null)<br/> instance_profile_name = optional(string, null)<br/> override_runner_role = optional(bool, null)<br/> runner_role_arn = optional(string, null)<br/> })</pre> | <pre>{<br/> "instance_profile_name": null,<br/> "override_instance_profile": false,<br/> "override_runner_role": false,<br/> "runner_role_arn": null<br/>}</pre> | no |
141142
| <a name="input_idle_config"></a> [idle\_config](#input\_idle\_config) | List of time periods, defined as a cron expression, to keep a minimum amount of runners active instead of scaling down to 0. By defining this list you can ensure that in time periods that match the cron expression within 5 seconds a runner is kept idle. | <pre>list(object({<br/> cron = string<br/> timeZone = string<br/> idleCount = number<br/> evictionStrategy = optional(string, "oldest_first")<br/> }))</pre> | `[]` | no |
142143
| <a name="input_instance_allocation_strategy"></a> [instance\_allocation\_strategy](#input\_instance\_allocation\_strategy) | The allocation strategy for spot instances. AWS recommends using `price-capacity-optimized` however the AWS default is `lowest-price`. | `string` | `"lowest-price"` | no |
143144
| <a name="input_instance_max_spot_price"></a> [instance\_max\_spot\_price](#input\_instance\_max\_spot\_price) | Max price price for spot instances per hour. This variable will be passed to the create fleet as max spot price for the fleet. | `string` | `null` | no |

0 commit comments

Comments
 (0)