Skip to content

Commit 104e591

Browse files
ci: fail PRs that hand-edit the generated API client (#701)
Adds a check that fails a pull request touching pkg/client/**/*_gen.go unless the PR was opened by the regen automation. The check needs no access to renderinc/api. Regenerating and diffing, as the sdk drift check does, requires reading the private api repo, and this repo's vars.APP_ID has no installation there. Since the regen automation keeps the committed client current, the question left for this repo is whether someone hand-edited generated code, which needs no schema. That makes the check a checkout and a git diff. Scoped to *_gen.go rather than all of pkg/client because handwritten files (client.go, cursorparams.go, oauth/, version/) live in that tree. Bot PRs are identified by author rather than head branch, since a branch name is chosen by whoever opens the PR. GitOrigin-RevId: dadaed1871f3127850d97315001b587109e59990
1 parent e264936 commit 104e591

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: Generated client guard
2+
3+
# Fails a pull request that hand-edits the generated REST client in pkg/client.
4+
#
5+
# Why this shape, rather than regenerating and diffing (which is what
6+
# renderinc/sdk .github/workflows/generated-client-drift.yml does):
7+
#
8+
# Regenerating requires reading the private renderinc/api. This repo's
9+
# vars.APP_ID is app 2491349 (render-oss-copybara-sync), which has no
10+
# installation on renderinc/api, so a regen-and-diff check here could not
11+
# authenticate and would fail before it did any work. This check therefore
12+
# needs no access to api at all: no schema, no generator, no Go toolchain.
13+
# It is a checkout and a git diff, so it runs in seconds.
14+
#
15+
# That is sufficient because the regen automation in api keeps the committed
16+
# client current: the client cannot fall behind the schema without a PR being
17+
# opened here. So the question left for this repo to answer is not "is the
18+
# client current" but "did a human hand-edit generated code", and answering
19+
# that needs no schema.
20+
21+
on:
22+
pull_request:
23+
branches: [main]
24+
25+
concurrency:
26+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
27+
cancel-in-progress: true
28+
29+
# contents:read is all this needs. It deliberately does not use
30+
# dorny/paths-filter, which reads the PR's changed files through the API and so
31+
# also needs pull-requests:read.
32+
permissions:
33+
contents: read
34+
35+
jobs:
36+
guard:
37+
name: Check generated client files were not hand-edited
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
41+
with:
42+
# Full history so the PR's merge base resolves.
43+
fetch-depth: 0
44+
45+
- name: Check for hand-edited generated files
46+
env:
47+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
48+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
49+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
50+
HAS_OVERRIDE: ${{ contains(github.event.pull_request.labels.*.name, 'manual-regen') }}
51+
run: |
52+
set -o errexit -o nounset -o pipefail
53+
54+
# The regen automation's PRs are expected to change generated files.
55+
#
56+
# Identified by author rather than by head branch (auto/regen-cli):
57+
# the head branch name is chosen by whoever opens the PR, so a human
58+
# could silently bypass this guard by naming their branch
59+
# auto/regen-cli. The author is set by GitHub from the identity that
60+
# opened the PR and cannot be spoofed by a contributor. The failure
61+
# modes also favour the author: if the automation's branch name
62+
# changes, an author check still passes its PRs, whereas a branch
63+
# check would start failing them.
64+
if [[ "$PR_AUTHOR" == "render-github-action-bot[bot]" ]]; then
65+
echo "PR authored by the regen automation ($PR_AUTHOR); generated client changes are expected."
66+
exit 0
67+
fi
68+
69+
# Generated files are exactly *_gen.go: that is the suffix the
70+
# generator writes and the only thing clean_generated_go_files
71+
# deletes. pkg/client also holds handwritten code (client.go,
72+
# cursorparams.go, oauth/, version/), so scoping this to the whole
73+
# directory would block legitimate edits to those files.
74+
#
75+
# Three-dot diff, so generated changes that landed on main after this
76+
# PR branched are not attributed to this PR.
77+
all_changed="$(git diff --name-only "$BASE_SHA...$HEAD_SHA")"
78+
changed="$(printf '%s\n' "$all_changed" | grep -E '^pkg/client/.*_gen\.go$' || true)"
79+
80+
if [[ -z "$changed" ]]; then
81+
echo "No generated client files changed."
82+
exit 0
83+
fi
84+
85+
if [[ "$HAS_OVERRIDE" == "true" ]]; then
86+
echo "Generated client files changed, allowed by the 'manual-regen' label:"
87+
printf '%s\n' "$changed"
88+
exit 0
89+
fi
90+
91+
echo "::error::This PR changes generated client files. Regenerate them instead of editing them by hand."
92+
echo
93+
echo "Generated client files changed by this PR:"
94+
printf '%s\n' "$changed" | sed 's/^/ /'
95+
echo
96+
cat <<'MSG'
97+
These files are written by public-api-schema/generate-cli.sh in
98+
renderinc/api and are overwritten on its next run, so hand edits to
99+
them are lost.
100+
101+
To change them, change the OpenAPI schema in renderinc/api and
102+
regenerate:
103+
104+
export RENDER_API_PATH=/path/to/api
105+
cd "$RENDER_API_PATH/public-api-schema" && ./generate-cli.sh
106+
107+
If you ran the generator yourself and this PR is that catch-up regen,
108+
add the 'manual-regen' label to this PR.
109+
MSG
110+
exit 1

0 commit comments

Comments
 (0)