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}