• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..10-Jun-2020-

README.mdH A D10-Jun-202012.4 KiB261197

merge.mdH A D10-Jun-20204.4 KiB10378

README.md

1# Contribute Bot Design
2
3Author: [@zombiezen][]<br>
4Date: 2018-08-01<br>
5Part of [#216][]
6
7[@zombiezen]: https://github.com/zombiezen
8[#216]: https://github.com/google/go-cloud/issues/216
9
10## Objective
11
12As a maintainer of the Go CDK, there are a number of conventions about format of
13issues, pull requests, and commits that keep the project tidy and easy to
14navigate. However, new contributors (and even seasoned contributors) forget to
15meet these conventions. It adds toil for maintainers of the Go CDK to correct
16contributions to match these conventions. The objective is to eliminate
17maintainer toil by notifying contributors of convention violations (or even
18correcting them automatically where possible).
19
20## Overview
21
22To reduce maintainer toil, this document proposes a [GitHub Application][] that
23does various checks. The bot will check the following (but likely more will be
24added):
25
26-   Issue title format
27-   PR title format
28-   Commit message format (one line summary, "Fixes" on last line of
29    description)
30-   License headers in new files
31-   Branches are in a fork, not on the main repository
32-   Removing the "In Progress" label from closed issues
33
34The bot is not responsible for any "correctness" checks: the continuous
35integration (CI) must be responsible for such checks.
36
37[GitHub Application]: https://developer.github.com/apps/about-apps/#about-github-apps
38
39## Background: GitHub Applications
40
41From the [GitHub documentation][about-github-apps]:
42
43> GitHub Apps are first-class actors within GitHub. A GitHub App acts on its own
44> behalf, taking actions via the API directly using its own identity, which
45> means you don't need to maintain a bot or service account as a separate user.
46>
47> GitHub Apps can be installed directly on organizations and user accounts and
48> granted access to specific repositories. They come with built-in webhooks and
49> narrow, specific permissions.
50
51GitHub Apps are somewhat like GCP service accounts in that they act as
52standalone roles. Administrators of the app can furnish keys for API
53authentication, which the server must use in order to act as the GitHub App. A
54GitHub App must surface a [webhook][] endpoint over HTTPS to listen for events
55from GitHub. At a minimum, this webhook must respond to ping and installation
56events.
57
58[about-github-apps]: https://developer.github.com/apps/about-apps/#about-github-apps
59[webhook]: https://developer.github.com/webhooks/
60
61## Detailed Design
62
63<img src="architecture.svg" alt="Architecture diagram showing GitHub communicating with a webhook endpoint, which sends to a Cloud Pub/Sub queue, which is read by an event worker, which then communicates with GitHub." height="329">
64
65A highly available webhook endpoint (running on App Engine Standard) will listen
66for webhook events from GitHub and write them to a Cloud Pub/Sub topic. A single
67event worker (running on Google Kubernetes Engine) will process the event queue
68and communicate with the GitHub API to make changes.
69
70Running the webhook on App Engine Standard keeps the availability of the webhook
71high and comes with a built-in domain name (appspot.com) and
72[HTTPS certificate][App Engine HTTPS certificate]. However, the Go CDK
73[does not support App Engine Standard][#210]. This is fine, since the endpoint
74is going to be quite fixed and not expected to change frequently.
75
76The event worker must only be running one instance and one subscription, since
77running multiple instances can cause confusing concurrent modifications. To
78ensure this level of scaling control (as well as leveraging the secrets), the
79event worker will run on Google Kubernetes Engine.
80
81[App Engine HTTPS certificate]: https://cloud.google.com/appengine/docs/standard/go/securing-custom-domains-with-ssl
82[#210]: https://github.com/google/go-cloud/issues/210
83
84### Event Worker Checks
85
86The primary logic of the event worker will be determining if an event matches
87some condition and then taking appropriate action (adding a comment or running
88checks). Event payloads include a snapshot of the affected resource, which
89allows the event worker to make checks without affecting the
90[GitHub API rate limit][]. Since the events are delivered asynchronously, the
91event worker must only use the event payload as a hint: if action might be
92required, it should query GitHub for the latest state.
93
94It is expected that more checks will be added over time, but they will likely be
95refinements of the rules below. Any increase in permission scope should be
96accompanied by a design doc.
97
98[GitHub API rate limit]: https://developer.github.com/v3/#rate-limiting
99
100#### Issue Title Format Check
101
102When an [IssuesEvent][] is received indicating an issue has been created or the
103title has changed:
104
1051.  The issue title is queried to see whether it follows the regex `^[a-z0-9/]+:
106    .*$`.
1071.  If the title does not follow the regex, then the issue's comments are
108    queried to see whether the bot has commented on the improper format since
109    the title change.
1101.  If no such comment exists, then the event worker issues a comment about the
111    title not matching the format.
112
113[IssuesEvent]: https://developer.github.com/v3/activity/events/types/#issuesevent
114
115#### Removing the "In Progress" Label
116
117When an [IssuesEvent][] is received and it indicates the issue has been closed:
118
1191.  The issue is queried to see whether it is still closed and check its labels.
1201.  If the issue is closed and its set of labels contains "In Progress", the "In
121    Progress" label is removed.
122
123#### Branches in Fork Check
124
125When an [PullRequestEvent][] is received and it indicates a pull request has
126been created:
127
1281.  Query the current pull request metadata.
1291.  If it is in the same repository and it is open, close it with a message to
130    the effect of please create pull requests from your own fork and to perhaps
131    delete the branch.
132
133[PullRequestEvent]: https://developer.github.com/v3/activity/events/types/#pullrequestevent
134
135#### Pull Request Linting
136
137When an [PullRequestEvent][] is received and it indicates a pull request has
138been created or synchronized (the content has changed):
139
1401.  If no check run exists for the latest commit on the pull request, create one
141    with an `in_progress` status. If one exists, don't do anything more.
1421.  Query the first differing commit's message. If the commit message's first
143    line does not match the regex `^[a-z0-9/]+: .*$`, then add a line item to
144    the status check output that the message format is wrong and fail the check.
145    If the commit message includes the regex (case-insensitive) `Fixes #[0-9]+`
146    but this match is not on its own set of lines at the end of the message,
147    then add a line item to the status check output that the message format is
148    wrong and fail the check.
1491.  Query the pull request's description. If the description includes an issue
150    number the first differing commit's message does not include it, add a line
151    item to the status check output to verify the commit message includes the
152    issue information. At most, the check's status will be neutral.
1531.  Check any added source files in the latest commit (**TODO: exact set of file
154    extensions**) to see if they include the license header. If they do not, add
155    an annotation to the first line of the file that a license header is missing
156    and fail the check.
1571.  If any new imports are added to go.mod, add an annotation to verify that the
158    license is one of the `notice`, `permissive`, or `unencumbered` licences
159    under the categories [defined by Google Open Source][thirdparty licenses].
1601.  If any commits in the pull request have a commit author that is not in the
161    [CONTRIBUTORS][] file, then add a line item to the status check output to
162    add the contributors to the file and fail the check.
163
164[thirdparty licenses]: https://opensource.google.com/docs/thirdparty/licenses/
165[CONTRIBUTORS]: https://github.com/google/go-cloud/blob/master/CONTRIBUTORS
166
167## Redundancy and Reliability
168
169The focus for this application is to ensure that events are not dropped on the
170ground. It is acceptable for the worker to be unavailable (impact is simply more
171maintainer toil), as long as the queue of events does not build up to the 7 day
172limit.
173
174GitHub requires the webhook have [a response time of &lt;10s][GitHub async work]
175and does not appear to redeliver events.
176
177*   Cloud Pub/Sub has a [99.95% uptime SLA][Cloud Pub/Sub SLA].
178*   App Engine has a [99.95% uptime SLA][App Engine SLA].
179*   GitHub itself has no SLA.
180
181Given these SLAs, the webhook listener's maximum uptime SLO is 99.9%.
182
183[GitHub async work]: https://developer.github.com/v3/guides/best-practices-for-integrators/#favor-asynchronous-work-over-synchronous
184[Cloud Pub/Sub SLA]: https://cloud.google.com/pubsub/sla
185[App Engine SLA]: https://cloud.google.com/appengine/sla
186
187## Security Considerations
188
189This GitHub application will have the following permissions:
190
191*   [Read repository metadata][] (enabled for every GitHub application)
192*   [Read & write checks][] (adding annotations to pull requests)
193*   [Read & write issues][] (adding comments to issues)
194*   [Read & write pull requests][] (adding comments to pull requests)
195
196GitHub applications are scoped to particular installed repositories, so they
197cannot act beyond the repositories that a human has explicitly "installed" them
198on. This prevents a compromised server or credentials from impacting more than
199just the Go CDK repository.
200
201Communication between GitHub and the webhook endpoint will take place over
202HTTPS, and the webhook endpoint will [verify the webhook secret][] in the
203payload to ensure that the event came from GitHub. Communication with Cloud
204Pub/Sub occurs over HTTPS, and messages in Cloud Pub/Sub are
205[encrypted at rest][Cloud Pub/Sub benefits].
206
207[Read repository metadata]: https://developer.github.com/v3/apps/permissions/#metadata-permissions
208[Read & write checks]: https://developer.github.com/v3/apps/permissions/#permission-on-checks
209[Read & write issues]: https://developer.github.com/v3/apps/permissions/#permission-on-issues
210[Read & write pull requests]: https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests
211[verify the webhook secret]: https://developer.github.com/webhooks/securing/
212[Cloud Pub/Sub benefits]: https://cloud.google.com/pubsub/docs/overview#benefits
213
214## Privacy Considerations
215
216The bot will only be operating on public GitHub data. Some of this data will be
217persisted in a Cloud Pub/Sub queue, but will be deleted after 7 days as per
218[Pub/Sub's retention policy][].
219
220[Pub/Sub's retention policy]: https://cloud.google.com/pubsub/docs/faq#persistent
221
222## Testing Plan
223
224This service is largely defined by its interaction with GitHub, but
225unfortunately, GitHub does not provide an emulator or staging environment. I see
226two possible approaches:
227
2281.  Create a fake GitHub API server. This has the benefit of making the test run
229    hermetic and fast, but also has a high upfront development cost. It's also
230    possible for the fake to not match real GitHub API behavior. However,
231    GitHub's docs include full sample requests and responses, so this might
232    still be viable.
233
2341.  Run tests against a sandbox repository. (This appears to be what
235    [GitHub recommends][install app on your account].) This is not hermetic, but
236    gives higher confidence in the tests. Each dev would create a GitHub
237    application and install it to a sandbox repository.
238
239    The local dev setup is somewhat complex, though, since the primary
240    interaction is through webhooks, which requires the local server to be
241    exposed over HTTPS. [GitHub recommends][GitHub app setup] using
242    [smee.io](https://smee.io) or [ngrok](https://ngrok.com/), but these are not
243    acceptable in all developer circumstances. A workaround would be to use an
244    isolated VM like Google Cloud Shell, but this does complicate local dev
245    story. Some of this cost could be mitigated by using HTTP replay, but
246    recording would still be a complicated setup.
247
248[install app on your account]: https://developer.github.com/apps/building-your-first-github-app/#install-the-app-on-your-account
249[GitHub app setup]: https://developer.github.com/apps/building-your-first-github-app/#one-time-setup
250
251## Related Work
252
253This bot serves a similar purpose to [GopherBot][], but the Go CDK only uses
254GitHub data, not Gerrit. While [Maintner][] exists as an eventual consistency
255cache to serve GitHub data, the Go CDK is not at the scale yet where rate
256limiting would be an issue, so the Contribute Bot just makes direct calls to the
257GitHub API.
258
259[GopherBot]: https://github.com/golang/go/wiki/gopherbot
260[Maintner]: https://godoc.org/golang.org/x/build/maintner
261