v0.4.0 72f8f2e

runner.connect.go RawHistoryFolder

1// SPDX-License-Identifier: MIT
2
3// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4//
5// Source: ermite/runner.proto
6
7package ermiteconnect
8
9import (
10	ermite "bnema.dev/ermite-api/gen/ermite"
11	connect "connectrpc.com/connect"
12	context "context"
13	errors "errors"
14	http "net/http"
15	strings "strings"
16)
17
18// This is a compile-time assertion to ensure that this generated file and the connect package are
19// compatible. If you get a compiler error that this constant is not defined, this code was
20// generated with a version of connect newer than the one compiled into your binary. You can fix the
21// problem by either regenerating this code with an older version of connect or updating the connect
22// version compiled into your binary.
23const _ = connect.IsAtLeastVersion1_13_0
24
25const (
26	// RunnerServiceName is the fully-qualified name of the RunnerService service.
27	RunnerServiceName = "ermite.RunnerService"
28)
29
30// These constants are the fully-qualified names of the RPCs defined in this package. They're
31// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32//
33// Note that these are different from the fully-qualified method names used by
34// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36// period.
37const (
38	// RunnerServiceRegisterRunnerProcedure is the fully-qualified name of the RunnerService's
39	// RegisterRunner RPC.
40	RunnerServiceRegisterRunnerProcedure = "/ermite.RunnerService/RegisterRunner"
41	// RunnerServiceFetchJobProcedure is the fully-qualified name of the RunnerService's FetchJob RPC.
42	RunnerServiceFetchJobProcedure = "/ermite.RunnerService/FetchJob"
43	// RunnerServiceUpdateJobProcedure is the fully-qualified name of the RunnerService's UpdateJob RPC.
44	RunnerServiceUpdateJobProcedure = "/ermite.RunnerService/UpdateJob"
45	// RunnerServiceAppendLogProcedure is the fully-qualified name of the RunnerService's AppendLog RPC.
46	RunnerServiceAppendLogProcedure = "/ermite.RunnerService/AppendLog"
47	// RunnerServiceHeartbeatProcedure is the fully-qualified name of the RunnerService's Heartbeat RPC.
48	RunnerServiceHeartbeatProcedure = "/ermite.RunnerService/Heartbeat"
49	// RunnerServiceWatchJobProcedure is the fully-qualified name of the RunnerService's WatchJob RPC.
50	RunnerServiceWatchJobProcedure = "/ermite.RunnerService/WatchJob"
51	// RunnerServiceCompleteJobProcedure is the fully-qualified name of the RunnerService's CompleteJob
52	// RPC.
53	RunnerServiceCompleteJobProcedure = "/ermite.RunnerService/CompleteJob"
54	// RunnerServiceResolveActionsProcedure is the fully-qualified name of the RunnerService's
55	// ResolveActions RPC.
56	RunnerServiceResolveActionsProcedure = "/ermite.RunnerService/ResolveActions"
57	// RunnerServiceUploadSummaryProcedure is the fully-qualified name of the RunnerService's
58	// UploadSummary RPC.
59	RunnerServiceUploadSummaryProcedure = "/ermite.RunnerService/UploadSummary"
60)
61
62// RunnerServiceClient is a client for the ermite.RunnerService service.
63type RunnerServiceClient interface {
64	// RegisterRunner registers a runner and returns its runner token, which is
65	// shown only in this response; the runner keeps it. The caller needs the
66	// runner:register scope on a token that is not restricted to repositories
67	// (a runner serves every repository), or the root token. Fails with
68	// unauthenticated without a valid token, permission_denied without the
69	// scope or with a repository-restricted token, invalid_argument if a field
70	// breaks a rule, and already_exists if name is taken by another runner and
71	// replace is not set.
72	RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
73	// FetchJob asks for a job to run (long poll). The server holds the call
74	// until a queued job matches the runner, or about 50 seconds (never more
75	// than 50) pass, then answers; without a job the response has no job and the
76	// runner calls again after retry_after. Clients set a call timeout above 50
77	// seconds (60 is suggested) and must not poll when they have no free
78	// capacity.
79	//
80	// A job matches when every label of its runs-on is among the labels of the
81	// runner (case-insensitive). A job is given to exactly one runner, even when
82	// many runners and many polls compete; the assignment is recorded before
83	// the response is sent. The job stays QUEUED for the owner until it is
84	// acknowledged.
85	//
86	// Acknowledgement. The runner's first Heartbeat for the job is the
87	// acknowledgement, and the runner sends it as soon as it has the job, before
88	// anything else. If none arrives within 30 seconds of the assignment (the
89	// response was lost, or the runner died), the server cancels the assignment:
90	// the job token is revoked, the job goes back to QUEUED with a fresh token
91	// and can be given to any runner, the same one included; nothing the old
92	// assignment stored is kept. This is not a failure of the job. A job whose
93	// assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE
94	// and an error saying so.
95	//
96	// Retry after a lost response. poll_id lets a client retry a poll safely:
97	// a call with the same poll_id as an earlier call of the same runner that
98	// assigned a job returns that same assignment (same Job, same token) as long
99	// as it has not been acknowledged or cancelled, instead of taking another.
100	//
101	// A runner that crashed or whose host restarted cannot resume a job: it does
102	// not keep the job token, so the job fails with the lost-runner error after
103	// the heartbeat timeout (see Heartbeat).
104	//
105	// Needs the runner token. Fails with unauthenticated when the runner was
106	// removed (also ending a call in progress) and invalid_argument if labels
107	// or poll_id break a rule.
108	FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
109	// UpdateJob reports the state of the steps of a job while it runs. Needs the
110	// job token. Each call is applied whole or not at all, and repeating an
111	// identical call changes nothing, so a runner may retry after a network
112	// error. To end the job use CompleteJob.
113	//
114	// Annotations are applied with the steps, in the same atomic step: for each
115	// step_number present in annotations, the annotations of that step stored
116	// so far are replaced by the ones of the request, so a retry never
117	// duplicates them. A step number absent from the request keeps its
118	// annotations. The step of an annotation need not be in the steps of the
119	// same call, and may already be COMPLETED: its annotations can still be
120	// replaced (the rule that a COMPLETED step cannot change applies to
121	// StepUpdate, not to annotations), until the job ends. A runner sends one
122	// UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
123	// so replacements apply in the order of the calls.
124	//
125	// Fails with invalid_argument if a rule is broken (including the annotation
126	// limits), with failed_precondition if a step goes back (a COMPLETED step
127	// cannot change, a step cannot go from IN_PROGRESS to PENDING) and with
128	// failed_precondition and a JobEnded detail if the job has ended.
129	UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
130	// AppendLog appends lines to the log of a job. Needs the job token.
131	//
132	// The log of a job is one sequence of lines, numbered from 0 across all
133	// steps; offset is the number of lines the server holds when the request is
134	// applied, that is the number of the first line of the request. Calls must
135	// go in order, one at a time, and are idempotent:
136	//
137	//   - offset equal to the server's line count: the lines are appended;
138	//   - offset below it: the first (count - offset) lines were already stored
139	//     (a retry after a lost response) and are skipped, and the rest is
140	//     appended; a request entirely stored changes nothing. The skipped lines
141	//     are not compared with the stored ones: a retry whose lines differ from
142	//     the first attempt is silently dropped, so a client must resend the
143	//     same lines at the same offset;
144	//   - offset above it: out_of_range (a gap is a bug of the client; the
145	//     message gives the line count the server holds).
146	//
147	// The response gives the line count after the call, so a client can check
148	// itself. Offsets resume only after a network error within the same runner
149	// process; they are not a way to resume a job after a crash.
150	//
151	// A request has at most 1,000 lines whose text adds up to at most 64 KiB
152	// (65,536 bytes), all in the same step; more is invalid_argument. A line
153	// longer than that is cut by the client into pieces, see LogLine.continued.
154	//
155	// Steps. The step of a request must be at least the step of the last line
156	// the server holds, because the log is ordered by step (applied lines only:
157	// lines skipped as already stored are not checked); a lower step is
158	// failed_precondition. Lines for a step that UpdateJob has not announced
159	// create it: it starts IN_PROGRESS at the time of its first line, with an
160	// empty name until an UpdateJob gives one.
161	//
162	// The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
163	// the server (text, timestamps and separators). The call that would exceed
164	// it appends nothing; the server adds a last line saying that the limit was
165	// reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and
166	// answers failed_precondition with a JobEnded detail like any call after the
167	// end of a job. The runner then stops the job.
168	//
169	// Lines sent after the end of the job are rejected, so a runner flushes all
170	// its AppendLog lines before it calls CompleteJob.
171	//
172	// Fails with invalid_argument if a rule is broken (step number, text with a
173	// line feed or invalid UTF-8, missing time, a continued first line).
174	AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
175	// Heartbeat tells the server that the runner is alive and still working on
176	// the job. Needs the job token. The first one acknowledges the assignment
177	// (see FetchJob). The runner then calls it every 30 seconds for as long as
178	// the job runs. The deadline is 2 minutes after the later of the last
179	// heartbeat and the start of the server (so a server restart does not fail
180	// the jobs of runners that could not reach it): past it the server ends the
181	// job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner:
182	// <name> lost communication with the server...". Only Heartbeat counts:
183	// other calls do not keep a job alive.
184	//
185	// A runner that gets unavailable (or any network error) retries Heartbeat
186	// every 5 seconds until it succeeds, instead of waiting for its next 30
187	// second tick, and keeps the job running meanwhile.
188	Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
189	// WatchJob streams what the runner must react to. Needs the job token. The
190	// runner opens it when it starts the job and keeps it open; it reopens it
191	// after any error other than unauthenticated and the failed_precondition
192	// that says the job ended, and after a clean close of
193	// the stream that did not end with a JobEnded message.
194	//
195	// When the stream opens and a cancellation has already been requested, the
196	// first message is that request, so a reconnect never misses one. Only the
197	// first reason is sent: a job already cancelled by the owner does not get a
198	// second message when its time budget expires. The server also sends a
199	// Keepalive at least every 30 seconds, which the client ignores. When the
200	// job ends the server sends JobEnded, then closes the stream without error.
201	// On a job that has already ended the call fails like the other RPCs
202	// (failed_precondition with a JobEnded detail).
203	//
204	// On a CancelRequest the runner stops the job as the official runner does
205	// (interrupt, then terminate, then kill) and reports the conclusion given
206	// for the reason with CompleteJob.
207	//
208	// The server does not trust the runner to obey. If no conclusion arrives
209	// within 60 seconds after a cancellation request, whatever the reason (user,
210	// fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED
211	// for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error
212	// saying that the runner did not answer, and the steps not completed are
213	// completed as cancelled. The runner's later CompleteJob returns that result.
214	WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error)
215	// CompleteJob ends a job: it reports the final steps, the outputs and the
216	// conclusion. Needs the job token, which stays valid for this RPC for 24
217	// hours after the end of the job, however it ended. The call is applied whole
218	// or not at all. Once the job has ended, the token works for no other RPC
219	// (they answer failed_precondition with a JobEnded detail).
220	//
221	// It is idempotent. The first call that finds the job running ends it and
222	// answers applied = true. Any later call, a retry after a lost response or a
223	// call after the server ended the job itself, is not validated and changes
224	// nothing: it answers OK with the stored conclusion and error and
225	// applied = false (a runner whose conclusion differs from the stored one
226	// simply logs it; the outputs it sent are dropped).
227	//
228	// Lines sent with AppendLog after this call are rejected: a runner flushes
229	// all its log lines first. Steps that were not reported COMPLETED when the job
230	// ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
231	// they never started and the job did not end by cancellation).
232	//
233	// Annotations are applied as in UpdateJob (replacement per step number, also
234	// for COMPLETED steps and steps not in steps), together with the conclusion.
235	// Summaries are not part of this call: a runner sends them with
236	// UploadSummary before it.
237	//
238	// Fails with invalid_argument if a rule is broken (including the output and
239	// annotation limits) and failed_precondition if a step goes back, as in
240	// UpdateJob.
241	CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error)
242	// ResolveActions turns the "uses" of workflow and action steps into what the
243	// runner needs to download the actions, as GitHub's action download info
244	// does. Needs the job token; the job_id must be the one of the token. It
245	// may be called as often as needed while the job runs (the runner calls it
246	// once per level of composite actions, see the runner's depth limit of 9).
247	// The runner then downloads each archive itself, from the sandbox, with the
248	// token of the ResolvedAction when there is one.
249	//
250	// The response has one ResolvedAction per ActionRef, in the same order. A
251	// reference that cannot be resolved (unknown repository, tag or branch, a
252	// repository without access, a "uses" that GitHub itself would reject) does
253	// not fail the call: its ResolvedAction has only ref and error, where error
254	// is the message GitHub gives for the same failure, which the runner prints
255	// as is. Only a failure of the call itself (credential, ended job, bad
256	// request, server error) is an RPC error; the runner then fails the job.
257	//
258	// Resolution is stable for a run attempt: the same ref resolves to the same
259	// sha (ResolvedAction.resolved_sha) for every job and every call of that
260	// attempt. A re-run attempt resolves again. Tokens are not shared: each
261	// job gets its own (ResolvedAction.token).
262	//
263	// Fails with unauthenticated or permission_denied as the other job RPCs,
264	// with failed_precondition and a JobEnded detail if the job has ended, and
265	// with invalid_argument if there are more than 100 refs or if a "uses" is a
266	// local action ("./path") or a Docker image ("docker://"), which the runner
267	// handles without the server.
268	ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error)
269	// UploadSummary stores the job summary of one step: the Markdown that the
270	// step wrote to the file named by GITHUB_STEP_SUMMARY. Needs the job token.
271	// The runner masks it before sending it, and sends it after the step ended
272	// and before CompleteJob.
273	//
274	// It is idempotent: the summary of a step is replaced by each call, so a
275	// retry changes nothing, and an empty markdown removes the summary of the
276	// step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
277	// server reads requests of up to 1 MiB plus the envelope (job id, step
278	// number, framing), so a summary of exactly 1 MiB is accepted; clients set
279	// their own message size limit above that.
280	//
281	// Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
282	// and with failed_precondition and a JobEnded detail if the job has ended.
283	UploadSummary(context.Context, *connect.Request[ermite.UploadSummaryRequest]) (*connect.Response[ermite.UploadSummaryResponse], error)
284}
285
286// NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it
287// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
288// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
289// connect.WithGRPCWeb() options.
290//
291// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
292// http://api.acme.com or https://acme.com/grpc).
293func NewRunnerServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RunnerServiceClient {
294	baseURL = strings.TrimRight(baseURL, "/")
295	runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
296	return &runnerServiceClient{
297		registerRunner: connect.NewClient[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse](
298			httpClient,
299			baseURL+RunnerServiceRegisterRunnerProcedure,
300			connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
301			connect.WithClientOptions(opts...),
302		),
303		fetchJob: connect.NewClient[ermite.FetchJobRequest, ermite.FetchJobResponse](
304			httpClient,
305			baseURL+RunnerServiceFetchJobProcedure,
306			connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
307			connect.WithClientOptions(opts...),
308		),
309		updateJob: connect.NewClient[ermite.UpdateJobRequest, ermite.UpdateJobResponse](
310			httpClient,
311			baseURL+RunnerServiceUpdateJobProcedure,
312			connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
313			connect.WithClientOptions(opts...),
314		),
315		appendLog: connect.NewClient[ermite.AppendLogRequest, ermite.AppendLogResponse](
316			httpClient,
317			baseURL+RunnerServiceAppendLogProcedure,
318			connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
319			connect.WithClientOptions(opts...),
320		),
321		heartbeat: connect.NewClient[ermite.HeartbeatRequest, ermite.HeartbeatResponse](
322			httpClient,
323			baseURL+RunnerServiceHeartbeatProcedure,
324			connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
325			connect.WithClientOptions(opts...),
326		),
327		watchJob: connect.NewClient[ermite.WatchJobRequest, ermite.WatchJobResponse](
328			httpClient,
329			baseURL+RunnerServiceWatchJobProcedure,
330			connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
331			connect.WithClientOptions(opts...),
332		),
333		completeJob: connect.NewClient[ermite.CompleteJobRequest, ermite.CompleteJobResponse](
334			httpClient,
335			baseURL+RunnerServiceCompleteJobProcedure,
336			connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")),
337			connect.WithClientOptions(opts...),
338		),
339		resolveActions: connect.NewClient[ermite.ResolveActionsRequest, ermite.ResolveActionsResponse](
340			httpClient,
341			baseURL+RunnerServiceResolveActionsProcedure,
342			connect.WithSchema(runnerServiceMethods.ByName("ResolveActions")),
343			connect.WithClientOptions(opts...),
344		),
345		uploadSummary: connect.NewClient[ermite.UploadSummaryRequest, ermite.UploadSummaryResponse](
346			httpClient,
347			baseURL+RunnerServiceUploadSummaryProcedure,
348			connect.WithSchema(runnerServiceMethods.ByName("UploadSummary")),
349			connect.WithClientOptions(opts...),
350		),
351	}
352}
353
354// runnerServiceClient implements RunnerServiceClient.
355type runnerServiceClient struct {
356	registerRunner *connect.Client[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse]
357	fetchJob       *connect.Client[ermite.FetchJobRequest, ermite.FetchJobResponse]
358	updateJob      *connect.Client[ermite.UpdateJobRequest, ermite.UpdateJobResponse]
359	appendLog      *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse]
360	heartbeat      *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse]
361	watchJob       *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse]
362	completeJob    *connect.Client[ermite.CompleteJobRequest, ermite.CompleteJobResponse]
363	resolveActions *connect.Client[ermite.ResolveActionsRequest, ermite.ResolveActionsResponse]
364	uploadSummary  *connect.Client[ermite.UploadSummaryRequest, ermite.UploadSummaryResponse]
365}
366
367// RegisterRunner calls ermite.RunnerService.RegisterRunner.
368func (c *runnerServiceClient) RegisterRunner(ctx context.Context, req *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
369	return c.registerRunner.CallUnary(ctx, req)
370}
371
372// FetchJob calls ermite.RunnerService.FetchJob.
373func (c *runnerServiceClient) FetchJob(ctx context.Context, req *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
374	return c.fetchJob.CallUnary(ctx, req)
375}
376
377// UpdateJob calls ermite.RunnerService.UpdateJob.
378func (c *runnerServiceClient) UpdateJob(ctx context.Context, req *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
379	return c.updateJob.CallUnary(ctx, req)
380}
381
382// AppendLog calls ermite.RunnerService.AppendLog.
383func (c *runnerServiceClient) AppendLog(ctx context.Context, req *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
384	return c.appendLog.CallUnary(ctx, req)
385}
386
387// Heartbeat calls ermite.RunnerService.Heartbeat.
388func (c *runnerServiceClient) Heartbeat(ctx context.Context, req *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
389	return c.heartbeat.CallUnary(ctx, req)
390}
391
392// WatchJob calls ermite.RunnerService.WatchJob.
393func (c *runnerServiceClient) WatchJob(ctx context.Context, req *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) {
394	return c.watchJob.CallServerStream(ctx, req)
395}
396
397// CompleteJob calls ermite.RunnerService.CompleteJob.
398func (c *runnerServiceClient) CompleteJob(ctx context.Context, req *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) {
399	return c.completeJob.CallUnary(ctx, req)
400}
401
402// ResolveActions calls ermite.RunnerService.ResolveActions.
403func (c *runnerServiceClient) ResolveActions(ctx context.Context, req *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) {
404	return c.resolveActions.CallUnary(ctx, req)
405}
406
407// UploadSummary calls ermite.RunnerService.UploadSummary.
408func (c *runnerServiceClient) UploadSummary(ctx context.Context, req *connect.Request[ermite.UploadSummaryRequest]) (*connect.Response[ermite.UploadSummaryResponse], error) {
409	return c.uploadSummary.CallUnary(ctx, req)
410}
411
412// RunnerServiceHandler is an implementation of the ermite.RunnerService service.
413type RunnerServiceHandler interface {
414	// RegisterRunner registers a runner and returns its runner token, which is
415	// shown only in this response; the runner keeps it. The caller needs the
416	// runner:register scope on a token that is not restricted to repositories
417	// (a runner serves every repository), or the root token. Fails with
418	// unauthenticated without a valid token, permission_denied without the
419	// scope or with a repository-restricted token, invalid_argument if a field
420	// breaks a rule, and already_exists if name is taken by another runner and
421	// replace is not set.
422	RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
423	// FetchJob asks for a job to run (long poll). The server holds the call
424	// until a queued job matches the runner, or about 50 seconds (never more
425	// than 50) pass, then answers; without a job the response has no job and the
426	// runner calls again after retry_after. Clients set a call timeout above 50
427	// seconds (60 is suggested) and must not poll when they have no free
428	// capacity.
429	//
430	// A job matches when every label of its runs-on is among the labels of the
431	// runner (case-insensitive). A job is given to exactly one runner, even when
432	// many runners and many polls compete; the assignment is recorded before
433	// the response is sent. The job stays QUEUED for the owner until it is
434	// acknowledged.
435	//
436	// Acknowledgement. The runner's first Heartbeat for the job is the
437	// acknowledgement, and the runner sends it as soon as it has the job, before
438	// anything else. If none arrives within 30 seconds of the assignment (the
439	// response was lost, or the runner died), the server cancels the assignment:
440	// the job token is revoked, the job goes back to QUEUED with a fresh token
441	// and can be given to any runner, the same one included; nothing the old
442	// assignment stored is kept. This is not a failure of the job. A job whose
443	// assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE
444	// and an error saying so.
445	//
446	// Retry after a lost response. poll_id lets a client retry a poll safely:
447	// a call with the same poll_id as an earlier call of the same runner that
448	// assigned a job returns that same assignment (same Job, same token) as long
449	// as it has not been acknowledged or cancelled, instead of taking another.
450	//
451	// A runner that crashed or whose host restarted cannot resume a job: it does
452	// not keep the job token, so the job fails with the lost-runner error after
453	// the heartbeat timeout (see Heartbeat).
454	//
455	// Needs the runner token. Fails with unauthenticated when the runner was
456	// removed (also ending a call in progress) and invalid_argument if labels
457	// or poll_id break a rule.
458	FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
459	// UpdateJob reports the state of the steps of a job while it runs. Needs the
460	// job token. Each call is applied whole or not at all, and repeating an
461	// identical call changes nothing, so a runner may retry after a network
462	// error. To end the job use CompleteJob.
463	//
464	// Annotations are applied with the steps, in the same atomic step: for each
465	// step_number present in annotations, the annotations of that step stored
466	// so far are replaced by the ones of the request, so a retry never
467	// duplicates them. A step number absent from the request keeps its
468	// annotations. The step of an annotation need not be in the steps of the
469	// same call, and may already be COMPLETED: its annotations can still be
470	// replaced (the rule that a COMPLETED step cannot change applies to
471	// StepUpdate, not to annotations), until the job ends. A runner sends one
472	// UpdateJob (or CompleteJob) at a time for a job, never concurrent calls,
473	// so replacements apply in the order of the calls.
474	//
475	// Fails with invalid_argument if a rule is broken (including the annotation
476	// limits), with failed_precondition if a step goes back (a COMPLETED step
477	// cannot change, a step cannot go from IN_PROGRESS to PENDING) and with
478	// failed_precondition and a JobEnded detail if the job has ended.
479	UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
480	// AppendLog appends lines to the log of a job. Needs the job token.
481	//
482	// The log of a job is one sequence of lines, numbered from 0 across all
483	// steps; offset is the number of lines the server holds when the request is
484	// applied, that is the number of the first line of the request. Calls must
485	// go in order, one at a time, and are idempotent:
486	//
487	//   - offset equal to the server's line count: the lines are appended;
488	//   - offset below it: the first (count - offset) lines were already stored
489	//     (a retry after a lost response) and are skipped, and the rest is
490	//     appended; a request entirely stored changes nothing. The skipped lines
491	//     are not compared with the stored ones: a retry whose lines differ from
492	//     the first attempt is silently dropped, so a client must resend the
493	//     same lines at the same offset;
494	//   - offset above it: out_of_range (a gap is a bug of the client; the
495	//     message gives the line count the server holds).
496	//
497	// The response gives the line count after the call, so a client can check
498	// itself. Offsets resume only after a network error within the same runner
499	// process; they are not a way to resume a job after a crash.
500	//
501	// A request has at most 1,000 lines whose text adds up to at most 64 KiB
502	// (65,536 bytes), all in the same step; more is invalid_argument. A line
503	// longer than that is cut by the client into pieces, see LogLine.continued.
504	//
505	// Steps. The step of a request must be at least the step of the last line
506	// the server holds, because the log is ordered by step (applied lines only:
507	// lines skipped as already stored are not checked); a lower step is
508	// failed_precondition. Lines for a step that UpdateJob has not announced
509	// create it: it starts IN_PROGRESS at the time of its first line, with an
510	// empty name until an UpdateJob gives one.
511	//
512	// The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
513	// the server (text, timestamps and separators). The call that would exceed
514	// it appends nothing; the server adds a last line saying that the limit was
515	// reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and
516	// answers failed_precondition with a JobEnded detail like any call after the
517	// end of a job. The runner then stops the job.
518	//
519	// Lines sent after the end of the job are rejected, so a runner flushes all
520	// its AppendLog lines before it calls CompleteJob.
521	//
522	// Fails with invalid_argument if a rule is broken (step number, text with a
523	// line feed or invalid UTF-8, missing time, a continued first line).
524	AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
525	// Heartbeat tells the server that the runner is alive and still working on
526	// the job. Needs the job token. The first one acknowledges the assignment
527	// (see FetchJob). The runner then calls it every 30 seconds for as long as
528	// the job runs. The deadline is 2 minutes after the later of the last
529	// heartbeat and the start of the server (so a server restart does not fail
530	// the jobs of runners that could not reach it): past it the server ends the
531	// job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner:
532	// <name> lost communication with the server...". Only Heartbeat counts:
533	// other calls do not keep a job alive.
534	//
535	// A runner that gets unavailable (or any network error) retries Heartbeat
536	// every 5 seconds until it succeeds, instead of waiting for its next 30
537	// second tick, and keeps the job running meanwhile.
538	Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
539	// WatchJob streams what the runner must react to. Needs the job token. The
540	// runner opens it when it starts the job and keeps it open; it reopens it
541	// after any error other than unauthenticated and the failed_precondition
542	// that says the job ended, and after a clean close of
543	// the stream that did not end with a JobEnded message.
544	//
545	// When the stream opens and a cancellation has already been requested, the
546	// first message is that request, so a reconnect never misses one. Only the
547	// first reason is sent: a job already cancelled by the owner does not get a
548	// second message when its time budget expires. The server also sends a
549	// Keepalive at least every 30 seconds, which the client ignores. When the
550	// job ends the server sends JobEnded, then closes the stream without error.
551	// On a job that has already ended the call fails like the other RPCs
552	// (failed_precondition with a JobEnded detail).
553	//
554	// On a CancelRequest the runner stops the job as the official runner does
555	// (interrupt, then terminate, then kill) and reports the conclusion given
556	// for the reason with CompleteJob.
557	//
558	// The server does not trust the runner to obey. If no conclusion arrives
559	// within 60 seconds after a cancellation request, whatever the reason (user,
560	// fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED
561	// for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error
562	// saying that the runner did not answer, and the steps not completed are
563	// completed as cancelled. The runner's later CompleteJob returns that result.
564	WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error
565	// CompleteJob ends a job: it reports the final steps, the outputs and the
566	// conclusion. Needs the job token, which stays valid for this RPC for 24
567	// hours after the end of the job, however it ended. The call is applied whole
568	// or not at all. Once the job has ended, the token works for no other RPC
569	// (they answer failed_precondition with a JobEnded detail).
570	//
571	// It is idempotent. The first call that finds the job running ends it and
572	// answers applied = true. Any later call, a retry after a lost response or a
573	// call after the server ended the job itself, is not validated and changes
574	// nothing: it answers OK with the stored conclusion and error and
575	// applied = false (a runner whose conclusion differs from the stored one
576	// simply logs it; the outputs it sent are dropped).
577	//
578	// Lines sent with AppendLog after this call are rejected: a runner flushes
579	// all its log lines first. Steps that were not reported COMPLETED when the job
580	// ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when
581	// they never started and the job did not end by cancellation).
582	//
583	// Annotations are applied as in UpdateJob (replacement per step number, also
584	// for COMPLETED steps and steps not in steps), together with the conclusion.
585	// Summaries are not part of this call: a runner sends them with
586	// UploadSummary before it.
587	//
588	// Fails with invalid_argument if a rule is broken (including the output and
589	// annotation limits) and failed_precondition if a step goes back, as in
590	// UpdateJob.
591	CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error)
592	// ResolveActions turns the "uses" of workflow and action steps into what the
593	// runner needs to download the actions, as GitHub's action download info
594	// does. Needs the job token; the job_id must be the one of the token. It
595	// may be called as often as needed while the job runs (the runner calls it
596	// once per level of composite actions, see the runner's depth limit of 9).
597	// The runner then downloads each archive itself, from the sandbox, with the
598	// token of the ResolvedAction when there is one.
599	//
600	// The response has one ResolvedAction per ActionRef, in the same order. A
601	// reference that cannot be resolved (unknown repository, tag or branch, a
602	// repository without access, a "uses" that GitHub itself would reject) does
603	// not fail the call: its ResolvedAction has only ref and error, where error
604	// is the message GitHub gives for the same failure, which the runner prints
605	// as is. Only a failure of the call itself (credential, ended job, bad
606	// request, server error) is an RPC error; the runner then fails the job.
607	//
608	// Resolution is stable for a run attempt: the same ref resolves to the same
609	// sha (ResolvedAction.resolved_sha) for every job and every call of that
610	// attempt. A re-run attempt resolves again. Tokens are not shared: each
611	// job gets its own (ResolvedAction.token).
612	//
613	// Fails with unauthenticated or permission_denied as the other job RPCs,
614	// with failed_precondition and a JobEnded detail if the job has ended, and
615	// with invalid_argument if there are more than 100 refs or if a "uses" is a
616	// local action ("./path") or a Docker image ("docker://"), which the runner
617	// handles without the server.
618	ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error)
619	// UploadSummary stores the job summary of one step: the Markdown that the
620	// step wrote to the file named by GITHUB_STEP_SUMMARY. Needs the job token.
621	// The runner masks it before sending it, and sends it after the step ended
622	// and before CompleteJob.
623	//
624	// It is idempotent: the summary of a step is replaced by each call, so a
625	// retry changes nothing, and an empty markdown removes the summary of the
626	// step. The Markdown is at most 1 MiB (1,048,576 bytes) and valid UTF-8. The
627	// server reads requests of up to 1 MiB plus the envelope (job id, step
628	// number, framing), so a summary of exactly 1 MiB is accepted; clients set
629	// their own message size limit above that.
630	//
631	// Fails with invalid_argument if a rule is broken (size, UTF-8, step number)
632	// and with failed_precondition and a JobEnded detail if the job has ended.
633	UploadSummary(context.Context, *connect.Request[ermite.UploadSummaryRequest]) (*connect.Response[ermite.UploadSummaryResponse], error)
634}
635
636// NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the
637// path on which to mount the handler and the handler itself.
638//
639// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
640// and JSON codecs. They also support gzip compression.
641func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
642	runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
643	runnerServiceRegisterRunnerHandler := connect.NewUnaryHandler(
644		RunnerServiceRegisterRunnerProcedure,
645		svc.RegisterRunner,
646		connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
647		connect.WithHandlerOptions(opts...),
648	)
649	runnerServiceFetchJobHandler := connect.NewUnaryHandler(
650		RunnerServiceFetchJobProcedure,
651		svc.FetchJob,
652		connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
653		connect.WithHandlerOptions(opts...),
654	)
655	runnerServiceUpdateJobHandler := connect.NewUnaryHandler(
656		RunnerServiceUpdateJobProcedure,
657		svc.UpdateJob,
658		connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
659		connect.WithHandlerOptions(opts...),
660	)
661	runnerServiceAppendLogHandler := connect.NewUnaryHandler(
662		RunnerServiceAppendLogProcedure,
663		svc.AppendLog,
664		connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
665		connect.WithHandlerOptions(opts...),
666	)
667	runnerServiceHeartbeatHandler := connect.NewUnaryHandler(
668		RunnerServiceHeartbeatProcedure,
669		svc.Heartbeat,
670		connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
671		connect.WithHandlerOptions(opts...),
672	)
673	runnerServiceWatchJobHandler := connect.NewServerStreamHandler(
674		RunnerServiceWatchJobProcedure,
675		svc.WatchJob,
676		connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
677		connect.WithHandlerOptions(opts...),
678	)
679	runnerServiceCompleteJobHandler := connect.NewUnaryHandler(
680		RunnerServiceCompleteJobProcedure,
681		svc.CompleteJob,
682		connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")),
683		connect.WithHandlerOptions(opts...),
684	)
685	runnerServiceResolveActionsHandler := connect.NewUnaryHandler(
686		RunnerServiceResolveActionsProcedure,
687		svc.ResolveActions,
688		connect.WithSchema(runnerServiceMethods.ByName("ResolveActions")),
689		connect.WithHandlerOptions(opts...),
690	)
691	runnerServiceUploadSummaryHandler := connect.NewUnaryHandler(
692		RunnerServiceUploadSummaryProcedure,
693		svc.UploadSummary,
694		connect.WithSchema(runnerServiceMethods.ByName("UploadSummary")),
695		connect.WithHandlerOptions(opts...),
696	)
697	return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
698		switch r.URL.Path {
699		case RunnerServiceRegisterRunnerProcedure:
700			runnerServiceRegisterRunnerHandler.ServeHTTP(w, r)
701		case RunnerServiceFetchJobProcedure:
702			runnerServiceFetchJobHandler.ServeHTTP(w, r)
703		case RunnerServiceUpdateJobProcedure:
704			runnerServiceUpdateJobHandler.ServeHTTP(w, r)
705		case RunnerServiceAppendLogProcedure:
706			runnerServiceAppendLogHandler.ServeHTTP(w, r)
707		case RunnerServiceHeartbeatProcedure:
708			runnerServiceHeartbeatHandler.ServeHTTP(w, r)
709		case RunnerServiceWatchJobProcedure:
710			runnerServiceWatchJobHandler.ServeHTTP(w, r)
711		case RunnerServiceCompleteJobProcedure:
712			runnerServiceCompleteJobHandler.ServeHTTP(w, r)
713		case RunnerServiceResolveActionsProcedure:
714			runnerServiceResolveActionsHandler.ServeHTTP(w, r)
715		case RunnerServiceUploadSummaryProcedure:
716			runnerServiceUploadSummaryHandler.ServeHTTP(w, r)
717		default:
718			http.NotFound(w, r)
719		}
720	})
721}
722
723// UnimplementedRunnerServiceHandler returns CodeUnimplemented from all methods.
724type UnimplementedRunnerServiceHandler struct{}
725
726func (UnimplementedRunnerServiceHandler) RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
727	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.RegisterRunner is not implemented"))
728}
729
730func (UnimplementedRunnerServiceHandler) FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
731	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.FetchJob is not implemented"))
732}
733
734func (UnimplementedRunnerServiceHandler) UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
735	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.UpdateJob is not implemented"))
736}
737
738func (UnimplementedRunnerServiceHandler) AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
739	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.AppendLog is not implemented"))
740}
741
742func (UnimplementedRunnerServiceHandler) Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
743	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.Heartbeat is not implemented"))
744}
745
746func (UnimplementedRunnerServiceHandler) WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error {
747	return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.WatchJob is not implemented"))
748}
749
750func (UnimplementedRunnerServiceHandler) CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) {
751	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.CompleteJob is not implemented"))
752}
753
754func (UnimplementedRunnerServiceHandler) ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) {
755	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.ResolveActions is not implemented"))
756}
757
758func (UnimplementedRunnerServiceHandler) UploadSummary(context.Context, *connect.Request[ermite.UploadSummaryRequest]) (*connect.Response[ermite.UploadSummaryResponse], error) {
759	return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.UploadSummary is not implemented"))
760}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 18

↑↓ moveenter openesc close