detached 8b00839

feat(api): add the CI runner and run services

RunnerService (RegisterRunner, FetchJob, UpdateJob, AppendLog, Heartbeat,
WatchJob) for runners, RunService (runs, jobs, logs, checks, runners) for
owners, RunUpdated and JobUpdated events, and the ci.runs, ci.runs.filters
and ci.runners feature ids.
commit
8b008393b6b54aec6b901b0225d235bd366f80c5
author
brice · · 2026-10-12 14:08 UTC
parent
86d856e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
13 files · +6006 -32
M README.md +4 -2
@@ -2,7 +2,7 @@
22
33 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
44
5-Status: early development. The contract defines seven services (below); the server that implements them is being built.
5+Status: early development. The contract defines nine services (below); the server that implements them is being built.
66
77 ```go
88 import "bnema.dev/ermite-api/gen/ermite"
@@ -22,13 +22,15 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
2222 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
2323 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
2424 | `EventService` | `Watch` (streamed, resumable) |
25+| `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed); for CI runners |
26+| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `CancelRun`, `RerunRun`, `ListChecks`, `ListRunners`, `RemoveRunner` |
2527 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
2628
2729 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
2830
2931 ## Layout
3032
31-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
33+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
3234 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
3335
3436 ## Generate
@@ -2,7 +2,7 @@
2 2
3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
4 4
5-Status: early development. The contract defines seven services (below); the server that implements them is being built.5+Status: early development. The contract defines nine services (below); the server that implements them is being built.
6 6
7 ```go7 ```go
8 import "bnema.dev/ermite-api/gen/ermite"8 import "bnema.dev/ermite-api/gen/ermite"
@@ -22,13 +22,15 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
22 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |22 | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` |
23 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |23 | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) |
24 | `EventService` | `Watch` (streamed, resumable) |24 | `EventService` | `Watch` (streamed, resumable) |
25+| `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed); for CI runners |
26+| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `CancelRun`, `RerunRun`, `ListChecks`, `ListRunners`, `RemoveRunner` |
25 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |27 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
26 28
27 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.29 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
28 30
29 ## Layout31 ## Layout
30 32
31-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`) plus shared `types`. Checked by `buf lint` and `buf breaking`.33+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
32 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.34 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
33 35
34 ## Generate36 ## Generate
M gen/ermite/ermiteconnect/event.connect.go +18 -2
@@ -49,7 +49,15 @@ type EventServiceClient interface {
4949 // for merge requests, merge_request_opened, merge_request_merged,
5050 // merge_request_closed and comment_added. Each one is delivered on the
5151 // "repo:<name>" topic of its repository and on "home", subject to the
52- // visibility of the repository. There is no event for a change of title or
52+ // visibility of the repository.
53+ //
54+ // Servers with the feature "ci.runs" also publish run_updated and
55+ // job_updated, only on the "repo:<name>" topic of their repository (never on
56+ // "home"), subject to the same visibility. They carry the state after the
57+ // change and are published when a run or a job is created, changes status
58+ // or conclusion, or when one of its steps starts or ends (never per log
59+ // line: follow a log with RunService.GetJobLog). A job update is published
60+ // before the update of its run. There is no event for a change of title or
5361 // description, nor for a resolved comment: after a gap, or to see such
5462 // changes, clients read the merge request again. A merge publishes
5563 // merge_request_merged, then pushed for the target branch.
@@ -111,7 +119,15 @@ type EventServiceHandler interface {
111119 // for merge requests, merge_request_opened, merge_request_merged,
112120 // merge_request_closed and comment_added. Each one is delivered on the
113121 // "repo:<name>" topic of its repository and on "home", subject to the
114- // visibility of the repository. There is no event for a change of title or
122+ // visibility of the repository.
123+ //
124+ // Servers with the feature "ci.runs" also publish run_updated and
125+ // job_updated, only on the "repo:<name>" topic of their repository (never on
126+ // "home"), subject to the same visibility. They carry the state after the
127+ // change and are published when a run or a job is created, changes status
128+ // or conclusion, or when one of its steps starts or ends (never per log
129+ // line: follow a log with RunService.GetJobLog). A job update is published
130+ // before the update of its run. There is no event for a change of title or
115131 // description, nor for a resolved comment: after a gap, or to see such
116132 // changes, clients read the merge request again. A merge publishes
117133 // merge_request_merged, then pushed for the target branch.
@@ -49,7 +49,15 @@ type EventServiceClient interface {
49 // for merge requests, merge_request_opened, merge_request_merged,49 // for merge requests, merge_request_opened, merge_request_merged,
50 // merge_request_closed and comment_added. Each one is delivered on the50 // merge_request_closed and comment_added. Each one is delivered on the
51 // "repo:<name>" topic of its repository and on "home", subject to the51 // "repo:<name>" topic of its repository and on "home", subject to the
52- // visibility of the repository. There is no event for a change of title or52+ // visibility of the repository.
53+ //
54+ // Servers with the feature "ci.runs" also publish run_updated and
55+ // job_updated, only on the "repo:<name>" topic of their repository (never on
56+ // "home"), subject to the same visibility. They carry the state after the
57+ // change and are published when a run or a job is created, changes status
58+ // or conclusion, or when one of its steps starts or ends (never per log
59+ // line: follow a log with RunService.GetJobLog). A job update is published
60+ // before the update of its run. There is no event for a change of title or
53 // description, nor for a resolved comment: after a gap, or to see such61 // description, nor for a resolved comment: after a gap, or to see such
54 // changes, clients read the merge request again. A merge publishes62 // changes, clients read the merge request again. A merge publishes
55 // merge_request_merged, then pushed for the target branch.63 // merge_request_merged, then pushed for the target branch.
@@ -111,7 +119,15 @@ type EventServiceHandler interface {
111 // for merge requests, merge_request_opened, merge_request_merged,119 // for merge requests, merge_request_opened, merge_request_merged,
112 // merge_request_closed and comment_added. Each one is delivered on the120 // merge_request_closed and comment_added. Each one is delivered on the
113 // "repo:<name>" topic of its repository and on "home", subject to the121 // "repo:<name>" topic of its repository and on "home", subject to the
114- // visibility of the repository. There is no event for a change of title or122+ // visibility of the repository.
123+ //
124+ // Servers with the feature "ci.runs" also publish run_updated and
125+ // job_updated, only on the "repo:<name>" topic of their repository (never on
126+ // "home"), subject to the same visibility. They carry the state after the
127+ // change and are published when a run or a job is created, changes status
128+ // or conclusion, or when one of its steps starts or ends (never per log
129+ // line: follow a log with RunService.GetJobLog). A job update is published
130+ // before the update of its run. There is no event for a change of title or
115 // description, nor for a resolved comment: after a gap, or to see such131 // description, nor for a resolved comment: after a gap, or to see such
116 // changes, clients read the merge request again. A merge publishes132 // changes, clients read the merge request again. A merge publishes
117 // merge_request_merged, then pushed for the target branch.133 // merge_request_merged, then pushed for the target branch.
A gen/ermite/ermiteconnect/run.connect.go +436 -0
@@ -0,0 +1,436 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/run.proto
6+
7+package ermiteconnect
8+
9+import (
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.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // RunServiceName is the fully-qualified name of the RunService service.
27+ RunServiceName = "ermite.RunService"
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.
37+const (
38+ // RunServiceListRunsProcedure is the fully-qualified name of the RunService's ListRuns RPC.
39+ RunServiceListRunsProcedure = "/ermite.RunService/ListRuns"
40+ // RunServiceGetRunProcedure is the fully-qualified name of the RunService's GetRun RPC.
41+ RunServiceGetRunProcedure = "/ermite.RunService/GetRun"
42+ // RunServiceGetJobProcedure is the fully-qualified name of the RunService's GetJob RPC.
43+ RunServiceGetJobProcedure = "/ermite.RunService/GetJob"
44+ // RunServiceGetJobLogProcedure is the fully-qualified name of the RunService's GetJobLog RPC.
45+ RunServiceGetJobLogProcedure = "/ermite.RunService/GetJobLog"
46+ // RunServiceCancelRunProcedure is the fully-qualified name of the RunService's CancelRun RPC.
47+ RunServiceCancelRunProcedure = "/ermite.RunService/CancelRun"
48+ // RunServiceRerunRunProcedure is the fully-qualified name of the RunService's RerunRun RPC.
49+ RunServiceRerunRunProcedure = "/ermite.RunService/RerunRun"
50+ // RunServiceListChecksProcedure is the fully-qualified name of the RunService's ListChecks RPC.
51+ RunServiceListChecksProcedure = "/ermite.RunService/ListChecks"
52+ // RunServiceListRunnersProcedure is the fully-qualified name of the RunService's ListRunners RPC.
53+ RunServiceListRunnersProcedure = "/ermite.RunService/ListRunners"
54+ // RunServiceRemoveRunnerProcedure is the fully-qualified name of the RunService's RemoveRunner RPC.
55+ RunServiceRemoveRunnerProcedure = "/ermite.RunService/RemoveRunner"
56+)
57+
58+// RunServiceClient is a client for the ermite.RunService service.
59+type RunServiceClient interface {
60+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
61+ // run is described by its latest attempt. The filters narrow the result; a
62+ // server without the feature "ci.runs.filters" ignores them and returns
63+ // every run, so a client that needs them checks the feature. Fails with
64+ // invalid_argument if a filter is malformed.
65+ ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error)
66+ // GetRun returns a run with its jobs and their steps, for the latest attempt
67+ // or for the one asked. Fails with not_found if the run or the attempt does
68+ // not exist.
69+ GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error)
70+ // GetJob returns one job with its steps. Fails with not_found if the job
71+ // or the attempt does not exist.
72+ GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
73+ // GetJobLog streams the lines of the log of a job, from a line number. The
74+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
75+ // text each.
76+ //
77+ // Without follow, the stream ends after the last line stored when the call
78+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
79+ // delay after the runner's AppendLog, and ends without error once the job has
80+ // completed and all its lines were sent; on a completed job follow changes
81+ // nothing. A client that loses the stream calls again with the offset after
82+ // the last line it received. The client may cancel at any time.
83+ //
84+ // Fails with not_found if the job, the attempt or its log does not exist (the
85+ // log is removed by the retention sweep, and a queued job has none: with
86+ // follow, the call waits for the first line instead), with out_of_range if
87+ // offset is beyond the end of the log of a job without follow, and with
88+ // resource_exhausted when there are too many concurrent log streams, in
89+ // total or from one client. Like Watch, a stream ends with unauthenticated
90+ // when the token was revoked or expired.
91+ GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest]) (*connect.ServerStreamForClient[ermite.GetJobLogResponse], error)
92+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
93+ // scope. Jobs that have not started are cancelled at once; jobs in progress
94+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
95+ // when they have ended, so the returned run may still be IN_PROGRESS.
96+ // Cancelling a run that already has a cancellation requested succeeds and
97+ // changes nothing. Fails with failed_precondition if the run has completed.
98+ CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error)
99+ // RerunRun starts a new attempt of a completed run: same run id and number,
100+ // attempt + 1, same commit and same workflow file as at the original run (the
101+ // file of the repository is not read again). Needs the ci:write scope. Fails
102+ // with failed_precondition if the latest attempt has not completed, or if
103+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
104+ RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error)
105+ // ListChecks lists the checks of a commit: for each job of each run on that
106+ // sha, the one of the latest attempt, ordered by creation time then name.
107+ // A commit without a run has no check: the list is empty, which is not an
108+ // error, and the server does not verify that the commit exists. Returns at
109+ // most 1,000 checks (truncated tells).
110+ ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error)
111+ // ListRunners lists the registered runners, ordered by name. Needs the
112+ // runner:register scope.
113+ ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error)
114+ // RemoveRunner removes a runner: its runner token stops working at once (a
115+ // FetchJob in progress ends with unauthenticated) and it no longer gets
116+ // jobs. A job it is running is not affected: it ends normally, or fails as
117+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
118+ // with not_found if the id is unknown.
119+ RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error)
120+}
121+
122+// NewRunServiceClient constructs a client for the ermite.RunService service. By default, it uses
123+// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
124+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
125+// connect.WithGRPCWeb() options.
126+//
127+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
128+// http://api.acme.com or https://acme.com/grpc).
129+func NewRunServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RunServiceClient {
130+ baseURL = strings.TrimRight(baseURL, "/")
131+ runServiceMethods := ermite.File_ermite_run_proto.Services().ByName("RunService").Methods()
132+ return &runServiceClient{
133+ listRuns: connect.NewClient[ermite.ListRunsRequest, ermite.ListRunsResponse](
134+ httpClient,
135+ baseURL+RunServiceListRunsProcedure,
136+ connect.WithSchema(runServiceMethods.ByName("ListRuns")),
137+ connect.WithClientOptions(opts...),
138+ ),
139+ getRun: connect.NewClient[ermite.GetRunRequest, ermite.GetRunResponse](
140+ httpClient,
141+ baseURL+RunServiceGetRunProcedure,
142+ connect.WithSchema(runServiceMethods.ByName("GetRun")),
143+ connect.WithClientOptions(opts...),
144+ ),
145+ getJob: connect.NewClient[ermite.GetJobRequest, ermite.GetJobResponse](
146+ httpClient,
147+ baseURL+RunServiceGetJobProcedure,
148+ connect.WithSchema(runServiceMethods.ByName("GetJob")),
149+ connect.WithClientOptions(opts...),
150+ ),
151+ getJobLog: connect.NewClient[ermite.GetJobLogRequest, ermite.GetJobLogResponse](
152+ httpClient,
153+ baseURL+RunServiceGetJobLogProcedure,
154+ connect.WithSchema(runServiceMethods.ByName("GetJobLog")),
155+ connect.WithClientOptions(opts...),
156+ ),
157+ cancelRun: connect.NewClient[ermite.CancelRunRequest, ermite.CancelRunResponse](
158+ httpClient,
159+ baseURL+RunServiceCancelRunProcedure,
160+ connect.WithSchema(runServiceMethods.ByName("CancelRun")),
161+ connect.WithClientOptions(opts...),
162+ ),
163+ rerunRun: connect.NewClient[ermite.RerunRunRequest, ermite.RerunRunResponse](
164+ httpClient,
165+ baseURL+RunServiceRerunRunProcedure,
166+ connect.WithSchema(runServiceMethods.ByName("RerunRun")),
167+ connect.WithClientOptions(opts...),
168+ ),
169+ listChecks: connect.NewClient[ermite.ListChecksRequest, ermite.ListChecksResponse](
170+ httpClient,
171+ baseURL+RunServiceListChecksProcedure,
172+ connect.WithSchema(runServiceMethods.ByName("ListChecks")),
173+ connect.WithClientOptions(opts...),
174+ ),
175+ listRunners: connect.NewClient[ermite.ListRunnersRequest, ermite.ListRunnersResponse](
176+ httpClient,
177+ baseURL+RunServiceListRunnersProcedure,
178+ connect.WithSchema(runServiceMethods.ByName("ListRunners")),
179+ connect.WithClientOptions(opts...),
180+ ),
181+ removeRunner: connect.NewClient[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse](
182+ httpClient,
183+ baseURL+RunServiceRemoveRunnerProcedure,
184+ connect.WithSchema(runServiceMethods.ByName("RemoveRunner")),
185+ connect.WithClientOptions(opts...),
186+ ),
187+ }
188+}
189+
190+// runServiceClient implements RunServiceClient.
191+type runServiceClient struct {
192+ listRuns *connect.Client[ermite.ListRunsRequest, ermite.ListRunsResponse]
193+ getRun *connect.Client[ermite.GetRunRequest, ermite.GetRunResponse]
194+ getJob *connect.Client[ermite.GetJobRequest, ermite.GetJobResponse]
195+ getJobLog *connect.Client[ermite.GetJobLogRequest, ermite.GetJobLogResponse]
196+ cancelRun *connect.Client[ermite.CancelRunRequest, ermite.CancelRunResponse]
197+ rerunRun *connect.Client[ermite.RerunRunRequest, ermite.RerunRunResponse]
198+ listChecks *connect.Client[ermite.ListChecksRequest, ermite.ListChecksResponse]
199+ listRunners *connect.Client[ermite.ListRunnersRequest, ermite.ListRunnersResponse]
200+ removeRunner *connect.Client[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse]
201+}
202+
203+// ListRuns calls ermite.RunService.ListRuns.
204+func (c *runServiceClient) ListRuns(ctx context.Context, req *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error) {
205+ return c.listRuns.CallUnary(ctx, req)
206+}
207+
208+// GetRun calls ermite.RunService.GetRun.
209+func (c *runServiceClient) GetRun(ctx context.Context, req *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error) {
210+ return c.getRun.CallUnary(ctx, req)
211+}
212+
213+// GetJob calls ermite.RunService.GetJob.
214+func (c *runServiceClient) GetJob(ctx context.Context, req *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error) {
215+ return c.getJob.CallUnary(ctx, req)
216+}
217+
218+// GetJobLog calls ermite.RunService.GetJobLog.
219+func (c *runServiceClient) GetJobLog(ctx context.Context, req *connect.Request[ermite.GetJobLogRequest]) (*connect.ServerStreamForClient[ermite.GetJobLogResponse], error) {
220+ return c.getJobLog.CallServerStream(ctx, req)
221+}
222+
223+// CancelRun calls ermite.RunService.CancelRun.
224+func (c *runServiceClient) CancelRun(ctx context.Context, req *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error) {
225+ return c.cancelRun.CallUnary(ctx, req)
226+}
227+
228+// RerunRun calls ermite.RunService.RerunRun.
229+func (c *runServiceClient) RerunRun(ctx context.Context, req *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error) {
230+ return c.rerunRun.CallUnary(ctx, req)
231+}
232+
233+// ListChecks calls ermite.RunService.ListChecks.
234+func (c *runServiceClient) ListChecks(ctx context.Context, req *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
235+ return c.listChecks.CallUnary(ctx, req)
236+}
237+
238+// ListRunners calls ermite.RunService.ListRunners.
239+func (c *runServiceClient) ListRunners(ctx context.Context, req *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error) {
240+ return c.listRunners.CallUnary(ctx, req)
241+}
242+
243+// RemoveRunner calls ermite.RunService.RemoveRunner.
244+func (c *runServiceClient) RemoveRunner(ctx context.Context, req *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error) {
245+ return c.removeRunner.CallUnary(ctx, req)
246+}
247+
248+// RunServiceHandler is an implementation of the ermite.RunService service.
249+type RunServiceHandler interface {
250+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
251+ // run is described by its latest attempt. The filters narrow the result; a
252+ // server without the feature "ci.runs.filters" ignores them and returns
253+ // every run, so a client that needs them checks the feature. Fails with
254+ // invalid_argument if a filter is malformed.
255+ ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error)
256+ // GetRun returns a run with its jobs and their steps, for the latest attempt
257+ // or for the one asked. Fails with not_found if the run or the attempt does
258+ // not exist.
259+ GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error)
260+ // GetJob returns one job with its steps. Fails with not_found if the job
261+ // or the attempt does not exist.
262+ GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
263+ // GetJobLog streams the lines of the log of a job, from a line number. The
264+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
265+ // text each.
266+ //
267+ // Without follow, the stream ends after the last line stored when the call
268+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
269+ // delay after the runner's AppendLog, and ends without error once the job has
270+ // completed and all its lines were sent; on a completed job follow changes
271+ // nothing. A client that loses the stream calls again with the offset after
272+ // the last line it received. The client may cancel at any time.
273+ //
274+ // Fails with not_found if the job, the attempt or its log does not exist (the
275+ // log is removed by the retention sweep, and a queued job has none: with
276+ // follow, the call waits for the first line instead), with out_of_range if
277+ // offset is beyond the end of the log of a job without follow, and with
278+ // resource_exhausted when there are too many concurrent log streams, in
279+ // total or from one client. Like Watch, a stream ends with unauthenticated
280+ // when the token was revoked or expired.
281+ GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest], *connect.ServerStream[ermite.GetJobLogResponse]) error
282+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
283+ // scope. Jobs that have not started are cancelled at once; jobs in progress
284+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
285+ // when they have ended, so the returned run may still be IN_PROGRESS.
286+ // Cancelling a run that already has a cancellation requested succeeds and
287+ // changes nothing. Fails with failed_precondition if the run has completed.
288+ CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error)
289+ // RerunRun starts a new attempt of a completed run: same run id and number,
290+ // attempt + 1, same commit and same workflow file as at the original run (the
291+ // file of the repository is not read again). Needs the ci:write scope. Fails
292+ // with failed_precondition if the latest attempt has not completed, or if
293+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
294+ RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error)
295+ // ListChecks lists the checks of a commit: for each job of each run on that
296+ // sha, the one of the latest attempt, ordered by creation time then name.
297+ // A commit without a run has no check: the list is empty, which is not an
298+ // error, and the server does not verify that the commit exists. Returns at
299+ // most 1,000 checks (truncated tells).
300+ ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error)
301+ // ListRunners lists the registered runners, ordered by name. Needs the
302+ // runner:register scope.
303+ ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error)
304+ // RemoveRunner removes a runner: its runner token stops working at once (a
305+ // FetchJob in progress ends with unauthenticated) and it no longer gets
306+ // jobs. A job it is running is not affected: it ends normally, or fails as
307+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
308+ // with not_found if the id is unknown.
309+ RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error)
310+}
311+
312+// NewRunServiceHandler builds an HTTP handler from the service implementation. It returns the path
313+// on which to mount the handler and the handler itself.
314+//
315+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
316+// and JSON codecs. They also support gzip compression.
317+func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
318+ runServiceMethods := ermite.File_ermite_run_proto.Services().ByName("RunService").Methods()
319+ runServiceListRunsHandler := connect.NewUnaryHandler(
320+ RunServiceListRunsProcedure,
321+ svc.ListRuns,
322+ connect.WithSchema(runServiceMethods.ByName("ListRuns")),
323+ connect.WithHandlerOptions(opts...),
324+ )
325+ runServiceGetRunHandler := connect.NewUnaryHandler(
326+ RunServiceGetRunProcedure,
327+ svc.GetRun,
328+ connect.WithSchema(runServiceMethods.ByName("GetRun")),
329+ connect.WithHandlerOptions(opts...),
330+ )
331+ runServiceGetJobHandler := connect.NewUnaryHandler(
332+ RunServiceGetJobProcedure,
333+ svc.GetJob,
334+ connect.WithSchema(runServiceMethods.ByName("GetJob")),
335+ connect.WithHandlerOptions(opts...),
336+ )
337+ runServiceGetJobLogHandler := connect.NewServerStreamHandler(
338+ RunServiceGetJobLogProcedure,
339+ svc.GetJobLog,
340+ connect.WithSchema(runServiceMethods.ByName("GetJobLog")),
341+ connect.WithHandlerOptions(opts...),
342+ )
343+ runServiceCancelRunHandler := connect.NewUnaryHandler(
344+ RunServiceCancelRunProcedure,
345+ svc.CancelRun,
346+ connect.WithSchema(runServiceMethods.ByName("CancelRun")),
347+ connect.WithHandlerOptions(opts...),
348+ )
349+ runServiceRerunRunHandler := connect.NewUnaryHandler(
350+ RunServiceRerunRunProcedure,
351+ svc.RerunRun,
352+ connect.WithSchema(runServiceMethods.ByName("RerunRun")),
353+ connect.WithHandlerOptions(opts...),
354+ )
355+ runServiceListChecksHandler := connect.NewUnaryHandler(
356+ RunServiceListChecksProcedure,
357+ svc.ListChecks,
358+ connect.WithSchema(runServiceMethods.ByName("ListChecks")),
359+ connect.WithHandlerOptions(opts...),
360+ )
361+ runServiceListRunnersHandler := connect.NewUnaryHandler(
362+ RunServiceListRunnersProcedure,
363+ svc.ListRunners,
364+ connect.WithSchema(runServiceMethods.ByName("ListRunners")),
365+ connect.WithHandlerOptions(opts...),
366+ )
367+ runServiceRemoveRunnerHandler := connect.NewUnaryHandler(
368+ RunServiceRemoveRunnerProcedure,
369+ svc.RemoveRunner,
370+ connect.WithSchema(runServiceMethods.ByName("RemoveRunner")),
371+ connect.WithHandlerOptions(opts...),
372+ )
373+ return "/ermite.RunService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
374+ switch r.URL.Path {
375+ case RunServiceListRunsProcedure:
376+ runServiceListRunsHandler.ServeHTTP(w, r)
377+ case RunServiceGetRunProcedure:
378+ runServiceGetRunHandler.ServeHTTP(w, r)
379+ case RunServiceGetJobProcedure:
380+ runServiceGetJobHandler.ServeHTTP(w, r)
381+ case RunServiceGetJobLogProcedure:
382+ runServiceGetJobLogHandler.ServeHTTP(w, r)
383+ case RunServiceCancelRunProcedure:
384+ runServiceCancelRunHandler.ServeHTTP(w, r)
385+ case RunServiceRerunRunProcedure:
386+ runServiceRerunRunHandler.ServeHTTP(w, r)
387+ case RunServiceListChecksProcedure:
388+ runServiceListChecksHandler.ServeHTTP(w, r)
389+ case RunServiceListRunnersProcedure:
390+ runServiceListRunnersHandler.ServeHTTP(w, r)
391+ case RunServiceRemoveRunnerProcedure:
392+ runServiceRemoveRunnerHandler.ServeHTTP(w, r)
393+ default:
394+ http.NotFound(w, r)
395+ }
396+ })
397+}
398+
399+// UnimplementedRunServiceHandler returns CodeUnimplemented from all methods.
400+type UnimplementedRunServiceHandler struct{}
401+
402+func (UnimplementedRunServiceHandler) ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error) {
403+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListRuns is not implemented"))
404+}
405+
406+func (UnimplementedRunServiceHandler) GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error) {
407+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetRun is not implemented"))
408+}
409+
410+func (UnimplementedRunServiceHandler) GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error) {
411+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetJob is not implemented"))
412+}
413+
414+func (UnimplementedRunServiceHandler) GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest], *connect.ServerStream[ermite.GetJobLogResponse]) error {
415+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetJobLog is not implemented"))
416+}
417+
418+func (UnimplementedRunServiceHandler) CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error) {
419+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.CancelRun is not implemented"))
420+}
421+
422+func (UnimplementedRunServiceHandler) RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error) {
423+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.RerunRun is not implemented"))
424+}
425+
426+func (UnimplementedRunServiceHandler) ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
427+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListChecks is not implemented"))
428+}
429+
430+func (UnimplementedRunServiceHandler) ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error) {
431+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListRunners is not implemented"))
432+}
433+
434+func (UnimplementedRunServiceHandler) RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error) {
435+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.RemoveRunner is not implemented"))
436+}
@@ -0,0 +1,436 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/run.proto
6+
7+package ermiteconnect
8+
9+import (
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.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // RunServiceName is the fully-qualified name of the RunService service.
27+ RunServiceName = "ermite.RunService"
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.
37+const (
38+ // RunServiceListRunsProcedure is the fully-qualified name of the RunService's ListRuns RPC.
39+ RunServiceListRunsProcedure = "/ermite.RunService/ListRuns"
40+ // RunServiceGetRunProcedure is the fully-qualified name of the RunService's GetRun RPC.
41+ RunServiceGetRunProcedure = "/ermite.RunService/GetRun"
42+ // RunServiceGetJobProcedure is the fully-qualified name of the RunService's GetJob RPC.
43+ RunServiceGetJobProcedure = "/ermite.RunService/GetJob"
44+ // RunServiceGetJobLogProcedure is the fully-qualified name of the RunService's GetJobLog RPC.
45+ RunServiceGetJobLogProcedure = "/ermite.RunService/GetJobLog"
46+ // RunServiceCancelRunProcedure is the fully-qualified name of the RunService's CancelRun RPC.
47+ RunServiceCancelRunProcedure = "/ermite.RunService/CancelRun"
48+ // RunServiceRerunRunProcedure is the fully-qualified name of the RunService's RerunRun RPC.
49+ RunServiceRerunRunProcedure = "/ermite.RunService/RerunRun"
50+ // RunServiceListChecksProcedure is the fully-qualified name of the RunService's ListChecks RPC.
51+ RunServiceListChecksProcedure = "/ermite.RunService/ListChecks"
52+ // RunServiceListRunnersProcedure is the fully-qualified name of the RunService's ListRunners RPC.
53+ RunServiceListRunnersProcedure = "/ermite.RunService/ListRunners"
54+ // RunServiceRemoveRunnerProcedure is the fully-qualified name of the RunService's RemoveRunner RPC.
55+ RunServiceRemoveRunnerProcedure = "/ermite.RunService/RemoveRunner"
56+)
57+
58+// RunServiceClient is a client for the ermite.RunService service.
59+type RunServiceClient interface {
60+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
61+ // run is described by its latest attempt. The filters narrow the result; a
62+ // server without the feature "ci.runs.filters" ignores them and returns
63+ // every run, so a client that needs them checks the feature. Fails with
64+ // invalid_argument if a filter is malformed.
65+ ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error)
66+ // GetRun returns a run with its jobs and their steps, for the latest attempt
67+ // or for the one asked. Fails with not_found if the run or the attempt does
68+ // not exist.
69+ GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error)
70+ // GetJob returns one job with its steps. Fails with not_found if the job
71+ // or the attempt does not exist.
72+ GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
73+ // GetJobLog streams the lines of the log of a job, from a line number. The
74+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
75+ // text each.
76+ //
77+ // Without follow, the stream ends after the last line stored when the call
78+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
79+ // delay after the runner's AppendLog, and ends without error once the job has
80+ // completed and all its lines were sent; on a completed job follow changes
81+ // nothing. A client that loses the stream calls again with the offset after
82+ // the last line it received. The client may cancel at any time.
83+ //
84+ // Fails with not_found if the job, the attempt or its log does not exist (the
85+ // log is removed by the retention sweep, and a queued job has none: with
86+ // follow, the call waits for the first line instead), with out_of_range if
87+ // offset is beyond the end of the log of a job without follow, and with
88+ // resource_exhausted when there are too many concurrent log streams, in
89+ // total or from one client. Like Watch, a stream ends with unauthenticated
90+ // when the token was revoked or expired.
91+ GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest]) (*connect.ServerStreamForClient[ermite.GetJobLogResponse], error)
92+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
93+ // scope. Jobs that have not started are cancelled at once; jobs in progress
94+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
95+ // when they have ended, so the returned run may still be IN_PROGRESS.
96+ // Cancelling a run that already has a cancellation requested succeeds and
97+ // changes nothing. Fails with failed_precondition if the run has completed.
98+ CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error)
99+ // RerunRun starts a new attempt of a completed run: same run id and number,
100+ // attempt + 1, same commit and same workflow file as at the original run (the
101+ // file of the repository is not read again). Needs the ci:write scope. Fails
102+ // with failed_precondition if the latest attempt has not completed, or if
103+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
104+ RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error)
105+ // ListChecks lists the checks of a commit: for each job of each run on that
106+ // sha, the one of the latest attempt, ordered by creation time then name.
107+ // A commit without a run has no check: the list is empty, which is not an
108+ // error, and the server does not verify that the commit exists. Returns at
109+ // most 1,000 checks (truncated tells).
110+ ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error)
111+ // ListRunners lists the registered runners, ordered by name. Needs the
112+ // runner:register scope.
113+ ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error)
114+ // RemoveRunner removes a runner: its runner token stops working at once (a
115+ // FetchJob in progress ends with unauthenticated) and it no longer gets
116+ // jobs. A job it is running is not affected: it ends normally, or fails as
117+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
118+ // with not_found if the id is unknown.
119+ RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error)
120+}
121+
122+// NewRunServiceClient constructs a client for the ermite.RunService service. By default, it uses
123+// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
124+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
125+// connect.WithGRPCWeb() options.
126+//
127+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
128+// http://api.acme.com or https://acme.com/grpc).
129+func NewRunServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RunServiceClient {
130+ baseURL = strings.TrimRight(baseURL, "/")
131+ runServiceMethods := ermite.File_ermite_run_proto.Services().ByName("RunService").Methods()
132+ return &runServiceClient{
133+ listRuns: connect.NewClient[ermite.ListRunsRequest, ermite.ListRunsResponse](
134+ httpClient,
135+ baseURL+RunServiceListRunsProcedure,
136+ connect.WithSchema(runServiceMethods.ByName("ListRuns")),
137+ connect.WithClientOptions(opts...),
138+ ),
139+ getRun: connect.NewClient[ermite.GetRunRequest, ermite.GetRunResponse](
140+ httpClient,
141+ baseURL+RunServiceGetRunProcedure,
142+ connect.WithSchema(runServiceMethods.ByName("GetRun")),
143+ connect.WithClientOptions(opts...),
144+ ),
145+ getJob: connect.NewClient[ermite.GetJobRequest, ermite.GetJobResponse](
146+ httpClient,
147+ baseURL+RunServiceGetJobProcedure,
148+ connect.WithSchema(runServiceMethods.ByName("GetJob")),
149+ connect.WithClientOptions(opts...),
150+ ),
151+ getJobLog: connect.NewClient[ermite.GetJobLogRequest, ermite.GetJobLogResponse](
152+ httpClient,
153+ baseURL+RunServiceGetJobLogProcedure,
154+ connect.WithSchema(runServiceMethods.ByName("GetJobLog")),
155+ connect.WithClientOptions(opts...),
156+ ),
157+ cancelRun: connect.NewClient[ermite.CancelRunRequest, ermite.CancelRunResponse](
158+ httpClient,
159+ baseURL+RunServiceCancelRunProcedure,
160+ connect.WithSchema(runServiceMethods.ByName("CancelRun")),
161+ connect.WithClientOptions(opts...),
162+ ),
163+ rerunRun: connect.NewClient[ermite.RerunRunRequest, ermite.RerunRunResponse](
164+ httpClient,
165+ baseURL+RunServiceRerunRunProcedure,
166+ connect.WithSchema(runServiceMethods.ByName("RerunRun")),
167+ connect.WithClientOptions(opts...),
168+ ),
169+ listChecks: connect.NewClient[ermite.ListChecksRequest, ermite.ListChecksResponse](
170+ httpClient,
171+ baseURL+RunServiceListChecksProcedure,
172+ connect.WithSchema(runServiceMethods.ByName("ListChecks")),
173+ connect.WithClientOptions(opts...),
174+ ),
175+ listRunners: connect.NewClient[ermite.ListRunnersRequest, ermite.ListRunnersResponse](
176+ httpClient,
177+ baseURL+RunServiceListRunnersProcedure,
178+ connect.WithSchema(runServiceMethods.ByName("ListRunners")),
179+ connect.WithClientOptions(opts...),
180+ ),
181+ removeRunner: connect.NewClient[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse](
182+ httpClient,
183+ baseURL+RunServiceRemoveRunnerProcedure,
184+ connect.WithSchema(runServiceMethods.ByName("RemoveRunner")),
185+ connect.WithClientOptions(opts...),
186+ ),
187+ }
188+}
189+
190+// runServiceClient implements RunServiceClient.
191+type runServiceClient struct {
192+ listRuns *connect.Client[ermite.ListRunsRequest, ermite.ListRunsResponse]
193+ getRun *connect.Client[ermite.GetRunRequest, ermite.GetRunResponse]
194+ getJob *connect.Client[ermite.GetJobRequest, ermite.GetJobResponse]
195+ getJobLog *connect.Client[ermite.GetJobLogRequest, ermite.GetJobLogResponse]
196+ cancelRun *connect.Client[ermite.CancelRunRequest, ermite.CancelRunResponse]
197+ rerunRun *connect.Client[ermite.RerunRunRequest, ermite.RerunRunResponse]
198+ listChecks *connect.Client[ermite.ListChecksRequest, ermite.ListChecksResponse]
199+ listRunners *connect.Client[ermite.ListRunnersRequest, ermite.ListRunnersResponse]
200+ removeRunner *connect.Client[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse]
201+}
202+
203+// ListRuns calls ermite.RunService.ListRuns.
204+func (c *runServiceClient) ListRuns(ctx context.Context, req *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error) {
205+ return c.listRuns.CallUnary(ctx, req)
206+}
207+
208+// GetRun calls ermite.RunService.GetRun.
209+func (c *runServiceClient) GetRun(ctx context.Context, req *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error) {
210+ return c.getRun.CallUnary(ctx, req)
211+}
212+
213+// GetJob calls ermite.RunService.GetJob.
214+func (c *runServiceClient) GetJob(ctx context.Context, req *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error) {
215+ return c.getJob.CallUnary(ctx, req)
216+}
217+
218+// GetJobLog calls ermite.RunService.GetJobLog.
219+func (c *runServiceClient) GetJobLog(ctx context.Context, req *connect.Request[ermite.GetJobLogRequest]) (*connect.ServerStreamForClient[ermite.GetJobLogResponse], error) {
220+ return c.getJobLog.CallServerStream(ctx, req)
221+}
222+
223+// CancelRun calls ermite.RunService.CancelRun.
224+func (c *runServiceClient) CancelRun(ctx context.Context, req *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error) {
225+ return c.cancelRun.CallUnary(ctx, req)
226+}
227+
228+// RerunRun calls ermite.RunService.RerunRun.
229+func (c *runServiceClient) RerunRun(ctx context.Context, req *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error) {
230+ return c.rerunRun.CallUnary(ctx, req)
231+}
232+
233+// ListChecks calls ermite.RunService.ListChecks.
234+func (c *runServiceClient) ListChecks(ctx context.Context, req *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
235+ return c.listChecks.CallUnary(ctx, req)
236+}
237+
238+// ListRunners calls ermite.RunService.ListRunners.
239+func (c *runServiceClient) ListRunners(ctx context.Context, req *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error) {
240+ return c.listRunners.CallUnary(ctx, req)
241+}
242+
243+// RemoveRunner calls ermite.RunService.RemoveRunner.
244+func (c *runServiceClient) RemoveRunner(ctx context.Context, req *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error) {
245+ return c.removeRunner.CallUnary(ctx, req)
246+}
247+
248+// RunServiceHandler is an implementation of the ermite.RunService service.
249+type RunServiceHandler interface {
250+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
251+ // run is described by its latest attempt. The filters narrow the result; a
252+ // server without the feature "ci.runs.filters" ignores them and returns
253+ // every run, so a client that needs them checks the feature. Fails with
254+ // invalid_argument if a filter is malformed.
255+ ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error)
256+ // GetRun returns a run with its jobs and their steps, for the latest attempt
257+ // or for the one asked. Fails with not_found if the run or the attempt does
258+ // not exist.
259+ GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error)
260+ // GetJob returns one job with its steps. Fails with not_found if the job
261+ // or the attempt does not exist.
262+ GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error)
263+ // GetJobLog streams the lines of the log of a job, from a line number. The
264+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
265+ // text each.
266+ //
267+ // Without follow, the stream ends after the last line stored when the call
268+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
269+ // delay after the runner's AppendLog, and ends without error once the job has
270+ // completed and all its lines were sent; on a completed job follow changes
271+ // nothing. A client that loses the stream calls again with the offset after
272+ // the last line it received. The client may cancel at any time.
273+ //
274+ // Fails with not_found if the job, the attempt or its log does not exist (the
275+ // log is removed by the retention sweep, and a queued job has none: with
276+ // follow, the call waits for the first line instead), with out_of_range if
277+ // offset is beyond the end of the log of a job without follow, and with
278+ // resource_exhausted when there are too many concurrent log streams, in
279+ // total or from one client. Like Watch, a stream ends with unauthenticated
280+ // when the token was revoked or expired.
281+ GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest], *connect.ServerStream[ermite.GetJobLogResponse]) error
282+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
283+ // scope. Jobs that have not started are cancelled at once; jobs in progress
284+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
285+ // when they have ended, so the returned run may still be IN_PROGRESS.
286+ // Cancelling a run that already has a cancellation requested succeeds and
287+ // changes nothing. Fails with failed_precondition if the run has completed.
288+ CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error)
289+ // RerunRun starts a new attempt of a completed run: same run id and number,
290+ // attempt + 1, same commit and same workflow file as at the original run (the
291+ // file of the repository is not read again). Needs the ci:write scope. Fails
292+ // with failed_precondition if the latest attempt has not completed, or if
293+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
294+ RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error)
295+ // ListChecks lists the checks of a commit: for each job of each run on that
296+ // sha, the one of the latest attempt, ordered by creation time then name.
297+ // A commit without a run has no check: the list is empty, which is not an
298+ // error, and the server does not verify that the commit exists. Returns at
299+ // most 1,000 checks (truncated tells).
300+ ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error)
301+ // ListRunners lists the registered runners, ordered by name. Needs the
302+ // runner:register scope.
303+ ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error)
304+ // RemoveRunner removes a runner: its runner token stops working at once (a
305+ // FetchJob in progress ends with unauthenticated) and it no longer gets
306+ // jobs. A job it is running is not affected: it ends normally, or fails as
307+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
308+ // with not_found if the id is unknown.
309+ RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error)
310+}
311+
312+// NewRunServiceHandler builds an HTTP handler from the service implementation. It returns the path
313+// on which to mount the handler and the handler itself.
314+//
315+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
316+// and JSON codecs. They also support gzip compression.
317+func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
318+ runServiceMethods := ermite.File_ermite_run_proto.Services().ByName("RunService").Methods()
319+ runServiceListRunsHandler := connect.NewUnaryHandler(
320+ RunServiceListRunsProcedure,
321+ svc.ListRuns,
322+ connect.WithSchema(runServiceMethods.ByName("ListRuns")),
323+ connect.WithHandlerOptions(opts...),
324+ )
325+ runServiceGetRunHandler := connect.NewUnaryHandler(
326+ RunServiceGetRunProcedure,
327+ svc.GetRun,
328+ connect.WithSchema(runServiceMethods.ByName("GetRun")),
329+ connect.WithHandlerOptions(opts...),
330+ )
331+ runServiceGetJobHandler := connect.NewUnaryHandler(
332+ RunServiceGetJobProcedure,
333+ svc.GetJob,
334+ connect.WithSchema(runServiceMethods.ByName("GetJob")),
335+ connect.WithHandlerOptions(opts...),
336+ )
337+ runServiceGetJobLogHandler := connect.NewServerStreamHandler(
338+ RunServiceGetJobLogProcedure,
339+ svc.GetJobLog,
340+ connect.WithSchema(runServiceMethods.ByName("GetJobLog")),
341+ connect.WithHandlerOptions(opts...),
342+ )
343+ runServiceCancelRunHandler := connect.NewUnaryHandler(
344+ RunServiceCancelRunProcedure,
345+ svc.CancelRun,
346+ connect.WithSchema(runServiceMethods.ByName("CancelRun")),
347+ connect.WithHandlerOptions(opts...),
348+ )
349+ runServiceRerunRunHandler := connect.NewUnaryHandler(
350+ RunServiceRerunRunProcedure,
351+ svc.RerunRun,
352+ connect.WithSchema(runServiceMethods.ByName("RerunRun")),
353+ connect.WithHandlerOptions(opts...),
354+ )
355+ runServiceListChecksHandler := connect.NewUnaryHandler(
356+ RunServiceListChecksProcedure,
357+ svc.ListChecks,
358+ connect.WithSchema(runServiceMethods.ByName("ListChecks")),
359+ connect.WithHandlerOptions(opts...),
360+ )
361+ runServiceListRunnersHandler := connect.NewUnaryHandler(
362+ RunServiceListRunnersProcedure,
363+ svc.ListRunners,
364+ connect.WithSchema(runServiceMethods.ByName("ListRunners")),
365+ connect.WithHandlerOptions(opts...),
366+ )
367+ runServiceRemoveRunnerHandler := connect.NewUnaryHandler(
368+ RunServiceRemoveRunnerProcedure,
369+ svc.RemoveRunner,
370+ connect.WithSchema(runServiceMethods.ByName("RemoveRunner")),
371+ connect.WithHandlerOptions(opts...),
372+ )
373+ return "/ermite.RunService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
374+ switch r.URL.Path {
375+ case RunServiceListRunsProcedure:
376+ runServiceListRunsHandler.ServeHTTP(w, r)
377+ case RunServiceGetRunProcedure:
378+ runServiceGetRunHandler.ServeHTTP(w, r)
379+ case RunServiceGetJobProcedure:
380+ runServiceGetJobHandler.ServeHTTP(w, r)
381+ case RunServiceGetJobLogProcedure:
382+ runServiceGetJobLogHandler.ServeHTTP(w, r)
383+ case RunServiceCancelRunProcedure:
384+ runServiceCancelRunHandler.ServeHTTP(w, r)
385+ case RunServiceRerunRunProcedure:
386+ runServiceRerunRunHandler.ServeHTTP(w, r)
387+ case RunServiceListChecksProcedure:
388+ runServiceListChecksHandler.ServeHTTP(w, r)
389+ case RunServiceListRunnersProcedure:
390+ runServiceListRunnersHandler.ServeHTTP(w, r)
391+ case RunServiceRemoveRunnerProcedure:
392+ runServiceRemoveRunnerHandler.ServeHTTP(w, r)
393+ default:
394+ http.NotFound(w, r)
395+ }
396+ })
397+}
398+
399+// UnimplementedRunServiceHandler returns CodeUnimplemented from all methods.
400+type UnimplementedRunServiceHandler struct{}
401+
402+func (UnimplementedRunServiceHandler) ListRuns(context.Context, *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error) {
403+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListRuns is not implemented"))
404+}
405+
406+func (UnimplementedRunServiceHandler) GetRun(context.Context, *connect.Request[ermite.GetRunRequest]) (*connect.Response[ermite.GetRunResponse], error) {
407+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetRun is not implemented"))
408+}
409+
410+func (UnimplementedRunServiceHandler) GetJob(context.Context, *connect.Request[ermite.GetJobRequest]) (*connect.Response[ermite.GetJobResponse], error) {
411+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetJob is not implemented"))
412+}
413+
414+func (UnimplementedRunServiceHandler) GetJobLog(context.Context, *connect.Request[ermite.GetJobLogRequest], *connect.ServerStream[ermite.GetJobLogResponse]) error {
415+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.GetJobLog is not implemented"))
416+}
417+
418+func (UnimplementedRunServiceHandler) CancelRun(context.Context, *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error) {
419+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.CancelRun is not implemented"))
420+}
421+
422+func (UnimplementedRunServiceHandler) RerunRun(context.Context, *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error) {
423+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.RerunRun is not implemented"))
424+}
425+
426+func (UnimplementedRunServiceHandler) ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
427+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListChecks is not implemented"))
428+}
429+
430+func (UnimplementedRunServiceHandler) ListRunners(context.Context, *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error) {
431+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListRunners is not implemented"))
432+}
433+
434+func (UnimplementedRunServiceHandler) RemoveRunner(context.Context, *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error) {
435+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.RemoveRunner is not implemented"))
436+}
A gen/ermite/ermiteconnect/runner.connect.go +415 -0
@@ -0,0 +1,415 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/runner.proto
6+
7+package ermiteconnect
8+
9+import (
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.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
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.
37+const (
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+)
52+
53+// RunnerServiceClient is a client for the ermite.RunnerService service.
54+type RunnerServiceClient interface {
55+ // RegisterRunner registers a runner and returns its runner token, which is
56+ // shown only in this response; the runner keeps it. The caller needs the
57+ // runner:register scope on a token that is not restricted to repositories
58+ // (a runner serves every repository), or the root token. Fails with
59+ // unauthenticated without a valid token, permission_denied without the
60+ // scope or with a repository-restricted token, invalid_argument if a field
61+ // breaks a rule, and already_exists if name is taken by another runner.
62+ RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
63+ // FetchJob asks for a job to run (long poll). The server holds the call
64+ // until a queued job matches the runner, or about 50 seconds (never more
65+ // than 50) pass, then answers; without a job the response has no job and the
66+ // runner calls again at once. Clients set a call timeout above 50 seconds
67+ // (60 is suggested) and must not poll when they have no free capacity.
68+ //
69+ // A job matches when every label of its runs-on is among the labels of the
70+ // runner (case-insensitive). A job is given to exactly one runner, even when
71+ // many runners and many polls compete; the assignment is recorded before
72+ // the response is sent, and the job is then in progress. If the response is
73+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
74+ // server fails it as a lost runner.
75+ //
76+ // Needs the runner token. Fails with unauthenticated when the runner was
77+ // removed (also ending a call in progress) and invalid_argument if labels
78+ // breaks a rule.
79+ FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
80+ // UpdateJob reports the state of the steps of a job and, once, how the job
81+ // ended. Needs the job token. Each call is applied whole or not at all, and
82+ // repeating an identical call changes nothing, so a runner may retry after a
83+ // network error.
84+ //
85+ // Fails with invalid_argument if a rule is broken (including the output
86+ // limits), and with failed_precondition if a step goes back (a COMPLETED
87+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
88+ // The call that carries a conclusion ends the job: its job token stops
89+ // working, the server completes the job and no further call is accepted,
90+ // so a retry of that last call after a lost response gets unauthenticated,
91+ // which means it was applied.
92+ UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
93+ // AppendLog appends lines to the log of a job. Needs the job token.
94+ //
95+ // The log of a job is one sequence of lines, numbered from 0 across all
96+ // steps; offset is the number of lines the server holds when the request is
97+ // applied, that is the number of the first line of the request. Calls must
98+ // go in order, one at a time, and are idempotent:
99+ //
100+ // - offset equal to the server's line count: the lines are appended;
101+ // - offset below it: the first (count - offset) lines were already stored
102+ // (a retry after a lost response) and are skipped, and the rest is
103+ // appended; a request entirely stored changes nothing;
104+ // - offset above it: out_of_range (a gap is a bug of the client; the
105+ // message gives the line count the server holds).
106+ //
107+ // The response gives the line count after the call, so a client can check
108+ // itself. A request has at most 1,000 lines whose text adds up to at most
109+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
110+ // line of more than 64 KiB is cut by the client into several lines.
111+ //
112+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
113+ // the server (text, timestamps and separators). The call that would exceed
114+ // it appends nothing; the server adds a last line saying that the limit was
115+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
116+ // answers resource_exhausted. The runner then stops the job and sends
117+ // nothing more: its calls get unauthenticated.
118+ //
119+ // Fails with invalid_argument if a rule is broken (step number, text with a
120+ // line feed or invalid UTF-8, missing time).
121+ AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
122+ // Heartbeat tells the server that the runner is alive and still working on
123+ // the job. Needs the job token. The runner calls it every 30 seconds for as
124+ // long as the job runs, from assignment to the end. Without a heartbeat for
125+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
126+ // "The self-hosted runner: <name> lost communication with the server...".
127+ // Only Heartbeat counts: other calls do not keep a job alive.
128+ Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
129+ // WatchJob streams the requests to stop a job. Needs the job token. The
130+ // runner opens it when it starts the job and keeps it open; it reopens it
131+ // after any error other than unauthenticated.
132+ //
133+ // When the stream opens and a cancellation has already been requested, the
134+ // first message is that request, so a reconnect never misses one. Only the
135+ // first reason is sent: a job already cancelled by the owner does not get a
136+ // second message when its timeout expires. The server also sends a
137+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
138+ // seconds, which the client ignores. The stream ends without error when
139+ // the job ends. The runner stops the job as the official runner does
140+ // (interrupt, then terminate, then kill) and reports the conclusion given
141+ // for the reason with UpdateJob.
142+ WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error)
143+}
144+
145+// NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it
146+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
147+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
148+// connect.WithGRPCWeb() options.
149+//
150+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
151+// http://api.acme.com or https://acme.com/grpc).
152+func NewRunnerServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RunnerServiceClient {
153+ baseURL = strings.TrimRight(baseURL, "/")
154+ runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
155+ return &runnerServiceClient{
156+ registerRunner: connect.NewClient[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse](
157+ httpClient,
158+ baseURL+RunnerServiceRegisterRunnerProcedure,
159+ connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
160+ connect.WithClientOptions(opts...),
161+ ),
162+ fetchJob: connect.NewClient[ermite.FetchJobRequest, ermite.FetchJobResponse](
163+ httpClient,
164+ baseURL+RunnerServiceFetchJobProcedure,
165+ connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
166+ connect.WithClientOptions(opts...),
167+ ),
168+ updateJob: connect.NewClient[ermite.UpdateJobRequest, ermite.UpdateJobResponse](
169+ httpClient,
170+ baseURL+RunnerServiceUpdateJobProcedure,
171+ connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
172+ connect.WithClientOptions(opts...),
173+ ),
174+ appendLog: connect.NewClient[ermite.AppendLogRequest, ermite.AppendLogResponse](
175+ httpClient,
176+ baseURL+RunnerServiceAppendLogProcedure,
177+ connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
178+ connect.WithClientOptions(opts...),
179+ ),
180+ heartbeat: connect.NewClient[ermite.HeartbeatRequest, ermite.HeartbeatResponse](
181+ httpClient,
182+ baseURL+RunnerServiceHeartbeatProcedure,
183+ connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
184+ connect.WithClientOptions(opts...),
185+ ),
186+ watchJob: connect.NewClient[ermite.WatchJobRequest, ermite.WatchJobResponse](
187+ httpClient,
188+ baseURL+RunnerServiceWatchJobProcedure,
189+ connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
190+ connect.WithClientOptions(opts...),
191+ ),
192+ }
193+}
194+
195+// runnerServiceClient implements RunnerServiceClient.
196+type runnerServiceClient struct {
197+ registerRunner *connect.Client[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse]
198+ fetchJob *connect.Client[ermite.FetchJobRequest, ermite.FetchJobResponse]
199+ updateJob *connect.Client[ermite.UpdateJobRequest, ermite.UpdateJobResponse]
200+ appendLog *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse]
201+ heartbeat *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse]
202+ watchJob *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse]
203+}
204+
205+// RegisterRunner calls ermite.RunnerService.RegisterRunner.
206+func (c *runnerServiceClient) RegisterRunner(ctx context.Context, req *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
207+ return c.registerRunner.CallUnary(ctx, req)
208+}
209+
210+// FetchJob calls ermite.RunnerService.FetchJob.
211+func (c *runnerServiceClient) FetchJob(ctx context.Context, req *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
212+ return c.fetchJob.CallUnary(ctx, req)
213+}
214+
215+// UpdateJob calls ermite.RunnerService.UpdateJob.
216+func (c *runnerServiceClient) UpdateJob(ctx context.Context, req *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
217+ return c.updateJob.CallUnary(ctx, req)
218+}
219+
220+// AppendLog calls ermite.RunnerService.AppendLog.
221+func (c *runnerServiceClient) AppendLog(ctx context.Context, req *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
222+ return c.appendLog.CallUnary(ctx, req)
223+}
224+
225+// Heartbeat calls ermite.RunnerService.Heartbeat.
226+func (c *runnerServiceClient) Heartbeat(ctx context.Context, req *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
227+ return c.heartbeat.CallUnary(ctx, req)
228+}
229+
230+// WatchJob calls ermite.RunnerService.WatchJob.
231+func (c *runnerServiceClient) WatchJob(ctx context.Context, req *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) {
232+ return c.watchJob.CallServerStream(ctx, req)
233+}
234+
235+// RunnerServiceHandler is an implementation of the ermite.RunnerService service.
236+type RunnerServiceHandler interface {
237+ // RegisterRunner registers a runner and returns its runner token, which is
238+ // shown only in this response; the runner keeps it. The caller needs the
239+ // runner:register scope on a token that is not restricted to repositories
240+ // (a runner serves every repository), or the root token. Fails with
241+ // unauthenticated without a valid token, permission_denied without the
242+ // scope or with a repository-restricted token, invalid_argument if a field
243+ // breaks a rule, and already_exists if name is taken by another runner.
244+ RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
245+ // FetchJob asks for a job to run (long poll). The server holds the call
246+ // until a queued job matches the runner, or about 50 seconds (never more
247+ // than 50) pass, then answers; without a job the response has no job and the
248+ // runner calls again at once. Clients set a call timeout above 50 seconds
249+ // (60 is suggested) and must not poll when they have no free capacity.
250+ //
251+ // A job matches when every label of its runs-on is among the labels of the
252+ // runner (case-insensitive). A job is given to exactly one runner, even when
253+ // many runners and many polls compete; the assignment is recorded before
254+ // the response is sent, and the job is then in progress. If the response is
255+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
256+ // server fails it as a lost runner.
257+ //
258+ // Needs the runner token. Fails with unauthenticated when the runner was
259+ // removed (also ending a call in progress) and invalid_argument if labels
260+ // breaks a rule.
261+ FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
262+ // UpdateJob reports the state of the steps of a job and, once, how the job
263+ // ended. Needs the job token. Each call is applied whole or not at all, and
264+ // repeating an identical call changes nothing, so a runner may retry after a
265+ // network error.
266+ //
267+ // Fails with invalid_argument if a rule is broken (including the output
268+ // limits), and with failed_precondition if a step goes back (a COMPLETED
269+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
270+ // The call that carries a conclusion ends the job: its job token stops
271+ // working, the server completes the job and no further call is accepted,
272+ // so a retry of that last call after a lost response gets unauthenticated,
273+ // which means it was applied.
274+ UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
275+ // AppendLog appends lines to the log of a job. Needs the job token.
276+ //
277+ // The log of a job is one sequence of lines, numbered from 0 across all
278+ // steps; offset is the number of lines the server holds when the request is
279+ // applied, that is the number of the first line of the request. Calls must
280+ // go in order, one at a time, and are idempotent:
281+ //
282+ // - offset equal to the server's line count: the lines are appended;
283+ // - offset below it: the first (count - offset) lines were already stored
284+ // (a retry after a lost response) and are skipped, and the rest is
285+ // appended; a request entirely stored changes nothing;
286+ // - offset above it: out_of_range (a gap is a bug of the client; the
287+ // message gives the line count the server holds).
288+ //
289+ // The response gives the line count after the call, so a client can check
290+ // itself. A request has at most 1,000 lines whose text adds up to at most
291+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
292+ // line of more than 64 KiB is cut by the client into several lines.
293+ //
294+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
295+ // the server (text, timestamps and separators). The call that would exceed
296+ // it appends nothing; the server adds a last line saying that the limit was
297+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
298+ // answers resource_exhausted. The runner then stops the job and sends
299+ // nothing more: its calls get unauthenticated.
300+ //
301+ // Fails with invalid_argument if a rule is broken (step number, text with a
302+ // line feed or invalid UTF-8, missing time).
303+ AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
304+ // Heartbeat tells the server that the runner is alive and still working on
305+ // the job. Needs the job token. The runner calls it every 30 seconds for as
306+ // long as the job runs, from assignment to the end. Without a heartbeat for
307+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
308+ // "The self-hosted runner: <name> lost communication with the server...".
309+ // Only Heartbeat counts: other calls do not keep a job alive.
310+ Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
311+ // WatchJob streams the requests to stop a job. Needs the job token. The
312+ // runner opens it when it starts the job and keeps it open; it reopens it
313+ // after any error other than unauthenticated.
314+ //
315+ // When the stream opens and a cancellation has already been requested, the
316+ // first message is that request, so a reconnect never misses one. Only the
317+ // first reason is sent: a job already cancelled by the owner does not get a
318+ // second message when its timeout expires. The server also sends a
319+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
320+ // seconds, which the client ignores. The stream ends without error when
321+ // the job ends. The runner stops the job as the official runner does
322+ // (interrupt, then terminate, then kill) and reports the conclusion given
323+ // for the reason with UpdateJob.
324+ WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error
325+}
326+
327+// NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the
328+// path on which to mount the handler and the handler itself.
329+//
330+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
331+// and JSON codecs. They also support gzip compression.
332+func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
333+ runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
334+ runnerServiceRegisterRunnerHandler := connect.NewUnaryHandler(
335+ RunnerServiceRegisterRunnerProcedure,
336+ svc.RegisterRunner,
337+ connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
338+ connect.WithHandlerOptions(opts...),
339+ )
340+ runnerServiceFetchJobHandler := connect.NewUnaryHandler(
341+ RunnerServiceFetchJobProcedure,
342+ svc.FetchJob,
343+ connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
344+ connect.WithHandlerOptions(opts...),
345+ )
346+ runnerServiceUpdateJobHandler := connect.NewUnaryHandler(
347+ RunnerServiceUpdateJobProcedure,
348+ svc.UpdateJob,
349+ connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
350+ connect.WithHandlerOptions(opts...),
351+ )
352+ runnerServiceAppendLogHandler := connect.NewUnaryHandler(
353+ RunnerServiceAppendLogProcedure,
354+ svc.AppendLog,
355+ connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
356+ connect.WithHandlerOptions(opts...),
357+ )
358+ runnerServiceHeartbeatHandler := connect.NewUnaryHandler(
359+ RunnerServiceHeartbeatProcedure,
360+ svc.Heartbeat,
361+ connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
362+ connect.WithHandlerOptions(opts...),
363+ )
364+ runnerServiceWatchJobHandler := connect.NewServerStreamHandler(
365+ RunnerServiceWatchJobProcedure,
366+ svc.WatchJob,
367+ connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
368+ connect.WithHandlerOptions(opts...),
369+ )
370+ return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
371+ switch r.URL.Path {
372+ case RunnerServiceRegisterRunnerProcedure:
373+ runnerServiceRegisterRunnerHandler.ServeHTTP(w, r)
374+ case RunnerServiceFetchJobProcedure:
375+ runnerServiceFetchJobHandler.ServeHTTP(w, r)
376+ case RunnerServiceUpdateJobProcedure:
377+ runnerServiceUpdateJobHandler.ServeHTTP(w, r)
378+ case RunnerServiceAppendLogProcedure:
379+ runnerServiceAppendLogHandler.ServeHTTP(w, r)
380+ case RunnerServiceHeartbeatProcedure:
381+ runnerServiceHeartbeatHandler.ServeHTTP(w, r)
382+ case RunnerServiceWatchJobProcedure:
383+ runnerServiceWatchJobHandler.ServeHTTP(w, r)
384+ default:
385+ http.NotFound(w, r)
386+ }
387+ })
388+}
389+
390+// UnimplementedRunnerServiceHandler returns CodeUnimplemented from all methods.
391+type UnimplementedRunnerServiceHandler struct{}
392+
393+func (UnimplementedRunnerServiceHandler) RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
394+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.RegisterRunner is not implemented"))
395+}
396+
397+func (UnimplementedRunnerServiceHandler) FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
398+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.FetchJob is not implemented"))
399+}
400+
401+func (UnimplementedRunnerServiceHandler) UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
402+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.UpdateJob is not implemented"))
403+}
404+
405+func (UnimplementedRunnerServiceHandler) AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
406+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.AppendLog is not implemented"))
407+}
408+
409+func (UnimplementedRunnerServiceHandler) Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
410+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.Heartbeat is not implemented"))
411+}
412+
413+func (UnimplementedRunnerServiceHandler) WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error {
414+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.WatchJob is not implemented"))
415+}
@@ -0,0 +1,415 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/runner.proto
6+
7+package ermiteconnect
8+
9+import (
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.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
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.
37+const (
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+)
52+
53+// RunnerServiceClient is a client for the ermite.RunnerService service.
54+type RunnerServiceClient interface {
55+ // RegisterRunner registers a runner and returns its runner token, which is
56+ // shown only in this response; the runner keeps it. The caller needs the
57+ // runner:register scope on a token that is not restricted to repositories
58+ // (a runner serves every repository), or the root token. Fails with
59+ // unauthenticated without a valid token, permission_denied without the
60+ // scope or with a repository-restricted token, invalid_argument if a field
61+ // breaks a rule, and already_exists if name is taken by another runner.
62+ RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
63+ // FetchJob asks for a job to run (long poll). The server holds the call
64+ // until a queued job matches the runner, or about 50 seconds (never more
65+ // than 50) pass, then answers; without a job the response has no job and the
66+ // runner calls again at once. Clients set a call timeout above 50 seconds
67+ // (60 is suggested) and must not poll when they have no free capacity.
68+ //
69+ // A job matches when every label of its runs-on is among the labels of the
70+ // runner (case-insensitive). A job is given to exactly one runner, even when
71+ // many runners and many polls compete; the assignment is recorded before
72+ // the response is sent, and the job is then in progress. If the response is
73+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
74+ // server fails it as a lost runner.
75+ //
76+ // Needs the runner token. Fails with unauthenticated when the runner was
77+ // removed (also ending a call in progress) and invalid_argument if labels
78+ // breaks a rule.
79+ FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
80+ // UpdateJob reports the state of the steps of a job and, once, how the job
81+ // ended. Needs the job token. Each call is applied whole or not at all, and
82+ // repeating an identical call changes nothing, so a runner may retry after a
83+ // network error.
84+ //
85+ // Fails with invalid_argument if a rule is broken (including the output
86+ // limits), and with failed_precondition if a step goes back (a COMPLETED
87+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
88+ // The call that carries a conclusion ends the job: its job token stops
89+ // working, the server completes the job and no further call is accepted,
90+ // so a retry of that last call after a lost response gets unauthenticated,
91+ // which means it was applied.
92+ UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
93+ // AppendLog appends lines to the log of a job. Needs the job token.
94+ //
95+ // The log of a job is one sequence of lines, numbered from 0 across all
96+ // steps; offset is the number of lines the server holds when the request is
97+ // applied, that is the number of the first line of the request. Calls must
98+ // go in order, one at a time, and are idempotent:
99+ //
100+ // - offset equal to the server's line count: the lines are appended;
101+ // - offset below it: the first (count - offset) lines were already stored
102+ // (a retry after a lost response) and are skipped, and the rest is
103+ // appended; a request entirely stored changes nothing;
104+ // - offset above it: out_of_range (a gap is a bug of the client; the
105+ // message gives the line count the server holds).
106+ //
107+ // The response gives the line count after the call, so a client can check
108+ // itself. A request has at most 1,000 lines whose text adds up to at most
109+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
110+ // line of more than 64 KiB is cut by the client into several lines.
111+ //
112+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
113+ // the server (text, timestamps and separators). The call that would exceed
114+ // it appends nothing; the server adds a last line saying that the limit was
115+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
116+ // answers resource_exhausted. The runner then stops the job and sends
117+ // nothing more: its calls get unauthenticated.
118+ //
119+ // Fails with invalid_argument if a rule is broken (step number, text with a
120+ // line feed or invalid UTF-8, missing time).
121+ AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
122+ // Heartbeat tells the server that the runner is alive and still working on
123+ // the job. Needs the job token. The runner calls it every 30 seconds for as
124+ // long as the job runs, from assignment to the end. Without a heartbeat for
125+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
126+ // "The self-hosted runner: <name> lost communication with the server...".
127+ // Only Heartbeat counts: other calls do not keep a job alive.
128+ Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
129+ // WatchJob streams the requests to stop a job. Needs the job token. The
130+ // runner opens it when it starts the job and keeps it open; it reopens it
131+ // after any error other than unauthenticated.
132+ //
133+ // When the stream opens and a cancellation has already been requested, the
134+ // first message is that request, so a reconnect never misses one. Only the
135+ // first reason is sent: a job already cancelled by the owner does not get a
136+ // second message when its timeout expires. The server also sends a
137+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
138+ // seconds, which the client ignores. The stream ends without error when
139+ // the job ends. The runner stops the job as the official runner does
140+ // (interrupt, then terminate, then kill) and reports the conclusion given
141+ // for the reason with UpdateJob.
142+ WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error)
143+}
144+
145+// NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it
146+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
147+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
148+// connect.WithGRPCWeb() options.
149+//
150+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
151+// http://api.acme.com or https://acme.com/grpc).
152+func NewRunnerServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RunnerServiceClient {
153+ baseURL = strings.TrimRight(baseURL, "/")
154+ runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
155+ return &runnerServiceClient{
156+ registerRunner: connect.NewClient[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse](
157+ httpClient,
158+ baseURL+RunnerServiceRegisterRunnerProcedure,
159+ connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
160+ connect.WithClientOptions(opts...),
161+ ),
162+ fetchJob: connect.NewClient[ermite.FetchJobRequest, ermite.FetchJobResponse](
163+ httpClient,
164+ baseURL+RunnerServiceFetchJobProcedure,
165+ connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
166+ connect.WithClientOptions(opts...),
167+ ),
168+ updateJob: connect.NewClient[ermite.UpdateJobRequest, ermite.UpdateJobResponse](
169+ httpClient,
170+ baseURL+RunnerServiceUpdateJobProcedure,
171+ connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
172+ connect.WithClientOptions(opts...),
173+ ),
174+ appendLog: connect.NewClient[ermite.AppendLogRequest, ermite.AppendLogResponse](
175+ httpClient,
176+ baseURL+RunnerServiceAppendLogProcedure,
177+ connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
178+ connect.WithClientOptions(opts...),
179+ ),
180+ heartbeat: connect.NewClient[ermite.HeartbeatRequest, ermite.HeartbeatResponse](
181+ httpClient,
182+ baseURL+RunnerServiceHeartbeatProcedure,
183+ connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
184+ connect.WithClientOptions(opts...),
185+ ),
186+ watchJob: connect.NewClient[ermite.WatchJobRequest, ermite.WatchJobResponse](
187+ httpClient,
188+ baseURL+RunnerServiceWatchJobProcedure,
189+ connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
190+ connect.WithClientOptions(opts...),
191+ ),
192+ }
193+}
194+
195+// runnerServiceClient implements RunnerServiceClient.
196+type runnerServiceClient struct {
197+ registerRunner *connect.Client[ermite.RegisterRunnerRequest, ermite.RegisterRunnerResponse]
198+ fetchJob *connect.Client[ermite.FetchJobRequest, ermite.FetchJobResponse]
199+ updateJob *connect.Client[ermite.UpdateJobRequest, ermite.UpdateJobResponse]
200+ appendLog *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse]
201+ heartbeat *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse]
202+ watchJob *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse]
203+}
204+
205+// RegisterRunner calls ermite.RunnerService.RegisterRunner.
206+func (c *runnerServiceClient) RegisterRunner(ctx context.Context, req *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
207+ return c.registerRunner.CallUnary(ctx, req)
208+}
209+
210+// FetchJob calls ermite.RunnerService.FetchJob.
211+func (c *runnerServiceClient) FetchJob(ctx context.Context, req *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
212+ return c.fetchJob.CallUnary(ctx, req)
213+}
214+
215+// UpdateJob calls ermite.RunnerService.UpdateJob.
216+func (c *runnerServiceClient) UpdateJob(ctx context.Context, req *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
217+ return c.updateJob.CallUnary(ctx, req)
218+}
219+
220+// AppendLog calls ermite.RunnerService.AppendLog.
221+func (c *runnerServiceClient) AppendLog(ctx context.Context, req *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
222+ return c.appendLog.CallUnary(ctx, req)
223+}
224+
225+// Heartbeat calls ermite.RunnerService.Heartbeat.
226+func (c *runnerServiceClient) Heartbeat(ctx context.Context, req *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
227+ return c.heartbeat.CallUnary(ctx, req)
228+}
229+
230+// WatchJob calls ermite.RunnerService.WatchJob.
231+func (c *runnerServiceClient) WatchJob(ctx context.Context, req *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) {
232+ return c.watchJob.CallServerStream(ctx, req)
233+}
234+
235+// RunnerServiceHandler is an implementation of the ermite.RunnerService service.
236+type RunnerServiceHandler interface {
237+ // RegisterRunner registers a runner and returns its runner token, which is
238+ // shown only in this response; the runner keeps it. The caller needs the
239+ // runner:register scope on a token that is not restricted to repositories
240+ // (a runner serves every repository), or the root token. Fails with
241+ // unauthenticated without a valid token, permission_denied without the
242+ // scope or with a repository-restricted token, invalid_argument if a field
243+ // breaks a rule, and already_exists if name is taken by another runner.
244+ RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error)
245+ // FetchJob asks for a job to run (long poll). The server holds the call
246+ // until a queued job matches the runner, or about 50 seconds (never more
247+ // than 50) pass, then answers; without a job the response has no job and the
248+ // runner calls again at once. Clients set a call timeout above 50 seconds
249+ // (60 is suggested) and must not poll when they have no free capacity.
250+ //
251+ // A job matches when every label of its runs-on is among the labels of the
252+ // runner (case-insensitive). A job is given to exactly one runner, even when
253+ // many runners and many polls compete; the assignment is recorded before
254+ // the response is sent, and the job is then in progress. If the response is
255+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
256+ // server fails it as a lost runner.
257+ //
258+ // Needs the runner token. Fails with unauthenticated when the runner was
259+ // removed (also ending a call in progress) and invalid_argument if labels
260+ // breaks a rule.
261+ FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error)
262+ // UpdateJob reports the state of the steps of a job and, once, how the job
263+ // ended. Needs the job token. Each call is applied whole or not at all, and
264+ // repeating an identical call changes nothing, so a runner may retry after a
265+ // network error.
266+ //
267+ // Fails with invalid_argument if a rule is broken (including the output
268+ // limits), and with failed_precondition if a step goes back (a COMPLETED
269+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
270+ // The call that carries a conclusion ends the job: its job token stops
271+ // working, the server completes the job and no further call is accepted,
272+ // so a retry of that last call after a lost response gets unauthenticated,
273+ // which means it was applied.
274+ UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error)
275+ // AppendLog appends lines to the log of a job. Needs the job token.
276+ //
277+ // The log of a job is one sequence of lines, numbered from 0 across all
278+ // steps; offset is the number of lines the server holds when the request is
279+ // applied, that is the number of the first line of the request. Calls must
280+ // go in order, one at a time, and are idempotent:
281+ //
282+ // - offset equal to the server's line count: the lines are appended;
283+ // - offset below it: the first (count - offset) lines were already stored
284+ // (a retry after a lost response) and are skipped, and the rest is
285+ // appended; a request entirely stored changes nothing;
286+ // - offset above it: out_of_range (a gap is a bug of the client; the
287+ // message gives the line count the server holds).
288+ //
289+ // The response gives the line count after the call, so a client can check
290+ // itself. A request has at most 1,000 lines whose text adds up to at most
291+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
292+ // line of more than 64 KiB is cut by the client into several lines.
293+ //
294+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
295+ // the server (text, timestamps and separators). The call that would exceed
296+ // it appends nothing; the server adds a last line saying that the limit was
297+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
298+ // answers resource_exhausted. The runner then stops the job and sends
299+ // nothing more: its calls get unauthenticated.
300+ //
301+ // Fails with invalid_argument if a rule is broken (step number, text with a
302+ // line feed or invalid UTF-8, missing time).
303+ AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error)
304+ // Heartbeat tells the server that the runner is alive and still working on
305+ // the job. Needs the job token. The runner calls it every 30 seconds for as
306+ // long as the job runs, from assignment to the end. Without a heartbeat for
307+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
308+ // "The self-hosted runner: <name> lost communication with the server...".
309+ // Only Heartbeat counts: other calls do not keep a job alive.
310+ Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error)
311+ // WatchJob streams the requests to stop a job. Needs the job token. The
312+ // runner opens it when it starts the job and keeps it open; it reopens it
313+ // after any error other than unauthenticated.
314+ //
315+ // When the stream opens and a cancellation has already been requested, the
316+ // first message is that request, so a reconnect never misses one. Only the
317+ // first reason is sent: a job already cancelled by the owner does not get a
318+ // second message when its timeout expires. The server also sends a
319+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
320+ // seconds, which the client ignores. The stream ends without error when
321+ // the job ends. The runner stops the job as the official runner does
322+ // (interrupt, then terminate, then kill) and reports the conclusion given
323+ // for the reason with UpdateJob.
324+ WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error
325+}
326+
327+// NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the
328+// path on which to mount the handler and the handler itself.
329+//
330+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
331+// and JSON codecs. They also support gzip compression.
332+func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
333+ runnerServiceMethods := ermite.File_ermite_runner_proto.Services().ByName("RunnerService").Methods()
334+ runnerServiceRegisterRunnerHandler := connect.NewUnaryHandler(
335+ RunnerServiceRegisterRunnerProcedure,
336+ svc.RegisterRunner,
337+ connect.WithSchema(runnerServiceMethods.ByName("RegisterRunner")),
338+ connect.WithHandlerOptions(opts...),
339+ )
340+ runnerServiceFetchJobHandler := connect.NewUnaryHandler(
341+ RunnerServiceFetchJobProcedure,
342+ svc.FetchJob,
343+ connect.WithSchema(runnerServiceMethods.ByName("FetchJob")),
344+ connect.WithHandlerOptions(opts...),
345+ )
346+ runnerServiceUpdateJobHandler := connect.NewUnaryHandler(
347+ RunnerServiceUpdateJobProcedure,
348+ svc.UpdateJob,
349+ connect.WithSchema(runnerServiceMethods.ByName("UpdateJob")),
350+ connect.WithHandlerOptions(opts...),
351+ )
352+ runnerServiceAppendLogHandler := connect.NewUnaryHandler(
353+ RunnerServiceAppendLogProcedure,
354+ svc.AppendLog,
355+ connect.WithSchema(runnerServiceMethods.ByName("AppendLog")),
356+ connect.WithHandlerOptions(opts...),
357+ )
358+ runnerServiceHeartbeatHandler := connect.NewUnaryHandler(
359+ RunnerServiceHeartbeatProcedure,
360+ svc.Heartbeat,
361+ connect.WithSchema(runnerServiceMethods.ByName("Heartbeat")),
362+ connect.WithHandlerOptions(opts...),
363+ )
364+ runnerServiceWatchJobHandler := connect.NewServerStreamHandler(
365+ RunnerServiceWatchJobProcedure,
366+ svc.WatchJob,
367+ connect.WithSchema(runnerServiceMethods.ByName("WatchJob")),
368+ connect.WithHandlerOptions(opts...),
369+ )
370+ return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
371+ switch r.URL.Path {
372+ case RunnerServiceRegisterRunnerProcedure:
373+ runnerServiceRegisterRunnerHandler.ServeHTTP(w, r)
374+ case RunnerServiceFetchJobProcedure:
375+ runnerServiceFetchJobHandler.ServeHTTP(w, r)
376+ case RunnerServiceUpdateJobProcedure:
377+ runnerServiceUpdateJobHandler.ServeHTTP(w, r)
378+ case RunnerServiceAppendLogProcedure:
379+ runnerServiceAppendLogHandler.ServeHTTP(w, r)
380+ case RunnerServiceHeartbeatProcedure:
381+ runnerServiceHeartbeatHandler.ServeHTTP(w, r)
382+ case RunnerServiceWatchJobProcedure:
383+ runnerServiceWatchJobHandler.ServeHTTP(w, r)
384+ default:
385+ http.NotFound(w, r)
386+ }
387+ })
388+}
389+
390+// UnimplementedRunnerServiceHandler returns CodeUnimplemented from all methods.
391+type UnimplementedRunnerServiceHandler struct{}
392+
393+func (UnimplementedRunnerServiceHandler) RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) {
394+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.RegisterRunner is not implemented"))
395+}
396+
397+func (UnimplementedRunnerServiceHandler) FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) {
398+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.FetchJob is not implemented"))
399+}
400+
401+func (UnimplementedRunnerServiceHandler) UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) {
402+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.UpdateJob is not implemented"))
403+}
404+
405+func (UnimplementedRunnerServiceHandler) AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) {
406+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.AppendLog is not implemented"))
407+}
408+
409+func (UnimplementedRunnerServiceHandler) Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) {
410+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.Heartbeat is not implemented"))
411+}
412+
413+func (UnimplementedRunnerServiceHandler) WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error {
414+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.WatchJob is not implemented"))
415+}
M gen/ermite/event.pb.go +174 -25 generated file: collapsed
A gen/ermite/run.pb.go +2278 -0 generated file: collapsed
A gen/ermite/runner.pb.go +1620 -0
@@ -0,0 +1,1620 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-go. DO NOT EDIT.
4+// versions:
5+// protoc-gen-go v1.36.12
6+// protoc (unknown)
7+// source: ermite/runner.proto
8+
9+package ermite
10+
11+import (
12+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
13+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
14+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
15+ reflect "reflect"
16+ sync "sync"
17+ unsafe "unsafe"
18+)
19+
20+const (
21+ // Verify that this generated code is sufficiently up-to-date.
22+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
23+ // Verify that runtime/protoimpl is sufficiently up-to-date.
24+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
25+)
26+
27+// RunStatus is where a run, a job or a check is in its life. It is the same
28+// as GitHub's status, without the rarely used values.
29+type RunStatus int32
30+
31+const (
32+ // Not set. In a filter it means every status. Never returned by the server.
33+ RunStatus_RUN_STATUS_UNSPECIFIED RunStatus = 0
34+ // Waiting: for a runner, for the jobs it needs, or for its turn
35+ // (max-parallel).
36+ RunStatus_RUN_STATUS_QUEUED RunStatus = 1
37+ // At least one job has been given to a runner and has not ended.
38+ RunStatus_RUN_STATUS_IN_PROGRESS RunStatus = 2
39+ // Over. The conclusion says how it ended.
40+ RunStatus_RUN_STATUS_COMPLETED RunStatus = 3
41+)
42+
43+// Enum value maps for RunStatus.
44+var (
45+ RunStatus_name = map[int32]string{
46+ 0: "RUN_STATUS_UNSPECIFIED",
47+ 1: "RUN_STATUS_QUEUED",
48+ 2: "RUN_STATUS_IN_PROGRESS",
49+ 3: "RUN_STATUS_COMPLETED",
50+ }
51+ RunStatus_value = map[string]int32{
52+ "RUN_STATUS_UNSPECIFIED": 0,
53+ "RUN_STATUS_QUEUED": 1,
54+ "RUN_STATUS_IN_PROGRESS": 2,
55+ "RUN_STATUS_COMPLETED": 3,
56+ }
57+)
58+
59+func (x RunStatus) Enum() *RunStatus {
60+ p := new(RunStatus)
61+ *p = x
62+ return p
63+}
64+
65+func (x RunStatus) String() string {
66+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
67+}
68+
69+func (RunStatus) Descriptor() protoreflect.EnumDescriptor {
70+ return file_ermite_runner_proto_enumTypes[0].Descriptor()
71+}
72+
73+func (RunStatus) Type() protoreflect.EnumType {
74+ return &file_ermite_runner_proto_enumTypes[0]
75+}
76+
77+func (x RunStatus) Number() protoreflect.EnumNumber {
78+ return protoreflect.EnumNumber(x)
79+}
80+
81+// Deprecated: Use RunStatus.Descriptor instead.
82+func (RunStatus) EnumDescriptor() ([]byte, []int) {
83+ return file_ermite_runner_proto_rawDescGZIP(), []int{0}
84+}
85+
86+// Conclusion is how a run, a job, a step or a check ended, with GitHub's
87+// values and meaning. It is unset (CONCLUSION_UNSPECIFIED) until the status is
88+// COMPLETED.
89+type Conclusion int32
90+
91+const (
92+ // Not completed yet. Never returned by the server for a completed item.
93+ Conclusion_CONCLUSION_UNSPECIFIED Conclusion = 0
94+ // Everything it ran succeeded.
95+ Conclusion_CONCLUSION_SUCCESS Conclusion = 1
96+ // A step failed, or the job failed for another reason described in
97+ // RunJob.error (runner lost, queue timeout, log limit, feature not
98+ // supported yet, invalid workflow file).
99+ Conclusion_CONCLUSION_FAILURE Conclusion = 2
100+ // Cancelled by the owner, or by fail-fast, or because the runner shut down.
101+ Conclusion_CONCLUSION_CANCELLED Conclusion = 3
102+ // Never ran: its "if" was false, or a job it needs did not succeed, or it was
103+ // not selected. Never reported by a runner.
104+ Conclusion_CONCLUSION_SKIPPED Conclusion = 4
105+ // Exceeded its timeout.
106+ Conclusion_CONCLUSION_TIMED_OUT Conclusion = 5
107+ // Neither a success nor a failure. Defined for parity with GitHub; no
108+ // workflow job ends this way yet. For merging it counts as a success.
109+ Conclusion_CONCLUSION_NEUTRAL Conclusion = 6
110+ // Waits for a human action (for example the approval of an environment).
111+ // Defined for parity with GitHub; the server does not produce it before
112+ // environments exist. For merging it counts as a failure.
113+ Conclusion_CONCLUSION_ACTION_REQUIRED Conclusion = 7
114+)
115+
116+// Enum value maps for Conclusion.
117+var (
118+ Conclusion_name = map[int32]string{
119+ 0: "CONCLUSION_UNSPECIFIED",
120+ 1: "CONCLUSION_SUCCESS",
121+ 2: "CONCLUSION_FAILURE",
122+ 3: "CONCLUSION_CANCELLED",
123+ 4: "CONCLUSION_SKIPPED",
124+ 5: "CONCLUSION_TIMED_OUT",
125+ 6: "CONCLUSION_NEUTRAL",
126+ 7: "CONCLUSION_ACTION_REQUIRED",
127+ }
128+ Conclusion_value = map[string]int32{
129+ "CONCLUSION_UNSPECIFIED": 0,
130+ "CONCLUSION_SUCCESS": 1,
131+ "CONCLUSION_FAILURE": 2,
132+ "CONCLUSION_CANCELLED": 3,
133+ "CONCLUSION_SKIPPED": 4,
134+ "CONCLUSION_TIMED_OUT": 5,
135+ "CONCLUSION_NEUTRAL": 6,
136+ "CONCLUSION_ACTION_REQUIRED": 7,
137+ }
138+)
139+
140+func (x Conclusion) Enum() *Conclusion {
141+ p := new(Conclusion)
142+ *p = x
143+ return p
144+}
145+
146+func (x Conclusion) String() string {
147+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
148+}
149+
150+func (Conclusion) Descriptor() protoreflect.EnumDescriptor {
151+ return file_ermite_runner_proto_enumTypes[1].Descriptor()
152+}
153+
154+func (Conclusion) Type() protoreflect.EnumType {
155+ return &file_ermite_runner_proto_enumTypes[1]
156+}
157+
158+func (x Conclusion) Number() protoreflect.EnumNumber {
159+ return protoreflect.EnumNumber(x)
160+}
161+
162+// Deprecated: Use Conclusion.Descriptor instead.
163+func (Conclusion) EnumDescriptor() ([]byte, []int) {
164+ return file_ermite_runner_proto_rawDescGZIP(), []int{1}
165+}
166+
167+// StepState is where a step is in its life.
168+type StepState int32
169+
170+const (
171+ // Not set. Rejected in requests, never returned by the server.
172+ StepState_STEP_STATE_UNSPECIFIED StepState = 0
173+ // Known but not started.
174+ StepState_STEP_STATE_PENDING StepState = 1
175+ // Running.
176+ StepState_STEP_STATE_IN_PROGRESS StepState = 2
177+ // Over. The conclusion says how it ended. A step that did not run because
178+ // of its "if" or an earlier failure is COMPLETED with CONCLUSION_SKIPPED.
179+ StepState_STEP_STATE_COMPLETED StepState = 3
180+)
181+
182+// Enum value maps for StepState.
183+var (
184+ StepState_name = map[int32]string{
185+ 0: "STEP_STATE_UNSPECIFIED",
186+ 1: "STEP_STATE_PENDING",
187+ 2: "STEP_STATE_IN_PROGRESS",
188+ 3: "STEP_STATE_COMPLETED",
189+ }
190+ StepState_value = map[string]int32{
191+ "STEP_STATE_UNSPECIFIED": 0,
192+ "STEP_STATE_PENDING": 1,
193+ "STEP_STATE_IN_PROGRESS": 2,
194+ "STEP_STATE_COMPLETED": 3,
195+ }
196+)
197+
198+func (x StepState) Enum() *StepState {
199+ p := new(StepState)
200+ *p = x
201+ return p
202+}
203+
204+func (x StepState) String() string {
205+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
206+}
207+
208+func (StepState) Descriptor() protoreflect.EnumDescriptor {
209+ return file_ermite_runner_proto_enumTypes[2].Descriptor()
210+}
211+
212+func (StepState) Type() protoreflect.EnumType {
213+ return &file_ermite_runner_proto_enumTypes[2]
214+}
215+
216+func (x StepState) Number() protoreflect.EnumNumber {
217+ return protoreflect.EnumNumber(x)
218+}
219+
220+// Deprecated: Use StepState.Descriptor instead.
221+func (StepState) EnumDescriptor() ([]byte, []int) {
222+ return file_ermite_runner_proto_rawDescGZIP(), []int{2}
223+}
224+
225+// RunnerIsolation is how a runner isolates each job from the host and from
226+// other jobs. The runner chooses it (auto-detection included) and reports it
227+// at registration; the job page shows it.
228+type RunnerIsolation int32
229+
230+const (
231+ // Not set. Rejected at registration, never returned by the server.
232+ RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED RunnerIsolation = 0
233+ // One micro-VM per job, destroyed at the end.
234+ RunnerIsolation_RUNNER_ISOLATION_VM RunnerIsolation = 1
235+ // One container per job with its own Docker daemon, run by the sysbox
236+ // runtime. Shares the host kernel.
237+ RunnerIsolation_RUNNER_ISOLATION_CONTAINER_SYSBOX RunnerIsolation = 2
238+ // One privileged container per job with its own Docker daemon. Shares the
239+ // host kernel and is equivalent to root on the host: the weakest mode.
240+ RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED RunnerIsolation = 3
241+)
242+
243+// Enum value maps for RunnerIsolation.
244+var (
245+ RunnerIsolation_name = map[int32]string{
246+ 0: "RUNNER_ISOLATION_UNSPECIFIED",
247+ 1: "RUNNER_ISOLATION_VM",
248+ 2: "RUNNER_ISOLATION_CONTAINER_SYSBOX",
249+ 3: "RUNNER_ISOLATION_CONTAINER_PRIVILEGED",
250+ }
251+ RunnerIsolation_value = map[string]int32{
252+ "RUNNER_ISOLATION_UNSPECIFIED": 0,
253+ "RUNNER_ISOLATION_VM": 1,
254+ "RUNNER_ISOLATION_CONTAINER_SYSBOX": 2,
255+ "RUNNER_ISOLATION_CONTAINER_PRIVILEGED": 3,
256+ }
257+)
258+
259+func (x RunnerIsolation) Enum() *RunnerIsolation {
260+ p := new(RunnerIsolation)
261+ *p = x
262+ return p
263+}
264+
265+func (x RunnerIsolation) String() string {
266+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
267+}
268+
269+func (RunnerIsolation) Descriptor() protoreflect.EnumDescriptor {
270+ return file_ermite_runner_proto_enumTypes[3].Descriptor()
271+}
272+
273+func (RunnerIsolation) Type() protoreflect.EnumType {
274+ return &file_ermite_runner_proto_enumTypes[3]
275+}
276+
277+func (x RunnerIsolation) Number() protoreflect.EnumNumber {
278+ return protoreflect.EnumNumber(x)
279+}
280+
281+// Deprecated: Use RunnerIsolation.Descriptor instead.
282+func (RunnerIsolation) EnumDescriptor() ([]byte, []int) {
283+ return file_ermite_runner_proto_rawDescGZIP(), []int{3}
284+}
285+
286+// CancelReason says why the server asks a runner to stop a job.
287+type CancelReason int32
288+
289+const (
290+ // Not set. A WatchJobResponse with this reason is a keep-alive and carries
291+ // no request.
292+ CancelReason_CANCEL_REASON_UNSPECIFIED CancelReason = 0
293+ // The owner cancelled the run (RunService.CancelRun, web or CLI). The job
294+ // ends CONCLUSION_CANCELLED.
295+ CancelReason_CANCEL_REASON_USER CancelReason = 1
296+ // Another job of the same matrix failed and the strategy has fail-fast. The
297+ // job ends CONCLUSION_CANCELLED.
298+ CancelReason_CANCEL_REASON_FAIL_FAST CancelReason = 2
299+ // The job exceeded its timeout_minutes without the runner ending it. The
300+ // job ends CONCLUSION_TIMED_OUT.
301+ CancelReason_CANCEL_REASON_TIMEOUT CancelReason = 3
302+)
303+
304+// Enum value maps for CancelReason.
305+var (
306+ CancelReason_name = map[int32]string{
307+ 0: "CANCEL_REASON_UNSPECIFIED",
308+ 1: "CANCEL_REASON_USER",
309+ 2: "CANCEL_REASON_FAIL_FAST",
310+ 3: "CANCEL_REASON_TIMEOUT",
311+ }
312+ CancelReason_value = map[string]int32{
313+ "CANCEL_REASON_UNSPECIFIED": 0,
314+ "CANCEL_REASON_USER": 1,
315+ "CANCEL_REASON_FAIL_FAST": 2,
316+ "CANCEL_REASON_TIMEOUT": 3,
317+ }
318+)
319+
320+func (x CancelReason) Enum() *CancelReason {
321+ p := new(CancelReason)
322+ *p = x
323+ return p
324+}
325+
326+func (x CancelReason) String() string {
327+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
328+}
329+
330+func (CancelReason) Descriptor() protoreflect.EnumDescriptor {
331+ return file_ermite_runner_proto_enumTypes[4].Descriptor()
332+}
333+
334+func (CancelReason) Type() protoreflect.EnumType {
335+ return &file_ermite_runner_proto_enumTypes[4]
336+}
337+
338+func (x CancelReason) Number() protoreflect.EnumNumber {
339+ return protoreflect.EnumNumber(x)
340+}
341+
342+// Deprecated: Use CancelReason.Descriptor instead.
343+func (CancelReason) EnumDescriptor() ([]byte, []int) {
344+ return file_ermite_runner_proto_rawDescGZIP(), []int{4}
345+}
346+
347+// RegisterRunnerRequest describes the runner to register.
348+type RegisterRunnerRequest struct {
349+ state protoimpl.MessageState `protogen:"open.v1"`
350+ // Name identifies the runner for people (job page, logs, ListRunners) and is
351+ // unique: 1 to 64 characters among letters, digits, ".", "_" and "-". It is
352+ // not a label and never matches runs-on.
353+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
354+ // Labels are the labels the runner adds to its implicit ones: at most 64,
355+ // each 1 to 64 characters among letters, digits, ".", "_" and "-", for
356+ // example "ubuntu-latest". Comparison is case-insensitive and duplicates
357+ // are removed. The server adds the implicit labels "self-hosted", the os
358+ // and the arch ("linux", "x64" or "arm64"); the runner name is not a label.
359+ Labels []string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"`
360+ // Version is the release of the runner, for example "v0.1.0", or "devel".
361+ // Informational, at most 64 characters.
362+ Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
363+ // Isolation is how the runner isolates jobs. Required.
364+ Isolation RunnerIsolation `protobuf:"varint,4,opt,name=isolation,proto3,enum=ermite.RunnerIsolation" json:"isolation,omitempty"`
365+ // Arch is the CPU architecture, in GitHub's names: "x64" or "arm64". Other
366+ // values are invalid_argument (the set may grow with compatible additions).
367+ Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"`
368+ // Os is the operating system: "linux". Other values are invalid_argument
369+ // (the set may grow with compatible additions).
370+ Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"`
371+ unknownFields protoimpl.UnknownFields
372+ sizeCache protoimpl.SizeCache
373+}
374+
375+func (x *RegisterRunnerRequest) Reset() {
376+ *x = RegisterRunnerRequest{}
377+ mi := &file_ermite_runner_proto_msgTypes[0]
378+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
379+ ms.StoreMessageInfo(mi)
380+}
381+
382+func (x *RegisterRunnerRequest) String() string {
383+ return protoimpl.X.MessageStringOf(x)
384+}
385+
386+func (*RegisterRunnerRequest) ProtoMessage() {}
387+
388+func (x *RegisterRunnerRequest) ProtoReflect() protoreflect.Message {
389+ mi := &file_ermite_runner_proto_msgTypes[0]
390+ if x != nil {
391+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
392+ if ms.LoadMessageInfo() == nil {
393+ ms.StoreMessageInfo(mi)
394+ }
395+ return ms
396+ }
397+ return mi.MessageOf(x)
398+}
399+
400+// Deprecated: Use RegisterRunnerRequest.ProtoReflect.Descriptor instead.
401+func (*RegisterRunnerRequest) Descriptor() ([]byte, []int) {
402+ return file_ermite_runner_proto_rawDescGZIP(), []int{0}
403+}
404+
405+func (x *RegisterRunnerRequest) GetName() string {
406+ if x != nil {
407+ return x.Name
408+ }
409+ return ""
410+}
411+
412+func (x *RegisterRunnerRequest) GetLabels() []string {
413+ if x != nil {
414+ return x.Labels
415+ }
416+ return nil
417+}
418+
419+func (x *RegisterRunnerRequest) GetVersion() string {
420+ if x != nil {
421+ return x.Version
422+ }
423+ return ""
424+}
425+
426+func (x *RegisterRunnerRequest) GetIsolation() RunnerIsolation {
427+ if x != nil {
428+ return x.Isolation
429+ }
430+ return RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED
431+}
432+
433+func (x *RegisterRunnerRequest) GetArch() string {
434+ if x != nil {
435+ return x.Arch
436+ }
437+ return ""
438+}
439+
440+func (x *RegisterRunnerRequest) GetOs() string {
441+ if x != nil {
442+ return x.Os
443+ }
444+ return ""
445+}
446+
447+// RegisterRunnerResponse holds the new runner and its secret.
448+type RegisterRunnerResponse struct {
449+ state protoimpl.MessageState `protogen:"open.v1"`
450+ // RunnerId identifies the runner in ListRunners and RemoveRunner. It is not
451+ // a secret.
452+ RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"`
453+ // RunnerToken is the secret of the runner, to present as "Authorization:
454+ // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is
455+ // returned once: the server keeps only its SHA-256, the runner must store
456+ // it (mode 0600). It stops working when the runner is removed.
457+ RunnerToken string `protobuf:"bytes,2,opt,name=runner_token,json=runnerToken,proto3" json:"runner_token,omitempty"`
458+ unknownFields protoimpl.UnknownFields
459+ sizeCache protoimpl.SizeCache
460+}
461+
462+func (x *RegisterRunnerResponse) Reset() {
463+ *x = RegisterRunnerResponse{}
464+ mi := &file_ermite_runner_proto_msgTypes[1]
465+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
466+ ms.StoreMessageInfo(mi)
467+}
468+
469+func (x *RegisterRunnerResponse) String() string {
470+ return protoimpl.X.MessageStringOf(x)
471+}
472+
473+func (*RegisterRunnerResponse) ProtoMessage() {}
474+
475+func (x *RegisterRunnerResponse) ProtoReflect() protoreflect.Message {
476+ mi := &file_ermite_runner_proto_msgTypes[1]
477+ if x != nil {
478+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
479+ if ms.LoadMessageInfo() == nil {
480+ ms.StoreMessageInfo(mi)
481+ }
482+ return ms
483+ }
484+ return mi.MessageOf(x)
485+}
486+
487+// Deprecated: Use RegisterRunnerResponse.ProtoReflect.Descriptor instead.
488+func (*RegisterRunnerResponse) Descriptor() ([]byte, []int) {
489+ return file_ermite_runner_proto_rawDescGZIP(), []int{1}
490+}
491+
492+func (x *RegisterRunnerResponse) GetRunnerId() string {
493+ if x != nil {
494+ return x.RunnerId
495+ }
496+ return ""
497+}
498+
499+func (x *RegisterRunnerResponse) GetRunnerToken() string {
500+ if x != nil {
501+ return x.RunnerToken
502+ }
503+ return ""
504+}
505+
506+// FetchJobRequest asks for a job.
507+type FetchJobRequest struct {
508+ state protoimpl.MessageState `protogen:"open.v1"`
509+ // Labels narrows the labels offered for this call. Empty means all the
510+ // labels of the runner, which is the normal case. Otherwise each one must be
511+ // a label of the runner (invalid_argument if not) and only jobs whose runs-on
512+ // labels are all among them are offered.
513+ Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"`
514+ unknownFields protoimpl.UnknownFields
515+ sizeCache protoimpl.SizeCache
516+}
517+
518+func (x *FetchJobRequest) Reset() {
519+ *x = FetchJobRequest{}
520+ mi := &file_ermite_runner_proto_msgTypes[2]
521+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
522+ ms.StoreMessageInfo(mi)
523+}
524+
525+func (x *FetchJobRequest) String() string {
526+ return protoimpl.X.MessageStringOf(x)
527+}
528+
529+func (*FetchJobRequest) ProtoMessage() {}
530+
531+func (x *FetchJobRequest) ProtoReflect() protoreflect.Message {
532+ mi := &file_ermite_runner_proto_msgTypes[2]
533+ if x != nil {
534+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
535+ if ms.LoadMessageInfo() == nil {
536+ ms.StoreMessageInfo(mi)
537+ }
538+ return ms
539+ }
540+ return mi.MessageOf(x)
541+}
542+
543+// Deprecated: Use FetchJobRequest.ProtoReflect.Descriptor instead.
544+func (*FetchJobRequest) Descriptor() ([]byte, []int) {
545+ return file_ermite_runner_proto_rawDescGZIP(), []int{2}
546+}
547+
548+func (x *FetchJobRequest) GetLabels() []string {
549+ if x != nil {
550+ return x.Labels
551+ }
552+ return nil
553+}
554+
555+// FetchJobResponse holds the job given to the runner, if any.
556+type FetchJobResponse struct {
557+ state protoimpl.MessageState `protogen:"open.v1"`
558+ // Job is the job to run. Unset when no job was available before the end of
559+ // the poll.
560+ Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"`
561+ unknownFields protoimpl.UnknownFields
562+ sizeCache protoimpl.SizeCache
563+}
564+
565+func (x *FetchJobResponse) Reset() {
566+ *x = FetchJobResponse{}
567+ mi := &file_ermite_runner_proto_msgTypes[3]
568+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
569+ ms.StoreMessageInfo(mi)
570+}
571+
572+func (x *FetchJobResponse) String() string {
573+ return protoimpl.X.MessageStringOf(x)
574+}
575+
576+func (*FetchJobResponse) ProtoMessage() {}
577+
578+func (x *FetchJobResponse) ProtoReflect() protoreflect.Message {
579+ mi := &file_ermite_runner_proto_msgTypes[3]
580+ if x != nil {
581+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
582+ if ms.LoadMessageInfo() == nil {
583+ ms.StoreMessageInfo(mi)
584+ }
585+ return ms
586+ }
587+ return mi.MessageOf(x)
588+}
589+
590+// Deprecated: Use FetchJobResponse.ProtoReflect.Descriptor instead.
591+func (*FetchJobResponse) Descriptor() ([]byte, []int) {
592+ return file_ermite_runner_proto_rawDescGZIP(), []int{3}
593+}
594+
595+func (x *FetchJobResponse) GetJob() *Job {
596+ if x != nil {
597+ return x.Job
598+ }
599+ return nil
600+}
601+
602+// Job is a job given to a runner: everything needed to run it, nothing more.
603+// The runner parses workflow_yaml itself; the protocol does not carry the
604+// workflow model, so it does not change when the model grows.
605+type Job struct {
606+ state protoimpl.MessageState `protogen:"open.v1"`
607+ // JobId identifies the job on the server. It is opaque, unique across
608+ // repositories and runs, and the same for every attempt of the job. With
609+ // attempt it names one execution.
610+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
611+ // RunId is the run the job belongs to, the "github.run_id". It is global to
612+ // the server.
613+ RunId int64 `protobuf:"varint,2,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
614+ // Attempt is the attempt of the run this job executes: 1 for the first, and
615+ // one more after each RunService.RerunRun. It is the "github.run_attempt".
616+ Attempt int32 `protobuf:"varint,3,opt,name=attempt,proto3" json:"attempt,omitempty"`
617+ // JobToken is the secret that authenticates this job's UpdateJob,
618+ // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer
619+ // <job_token>"). It is random, valid until the job ends, survives a server
620+ // restart and is shown only in this response. It must stay in the runner
621+ // host process and never reach the job's code.
622+ JobToken string `protobuf:"bytes,4,opt,name=job_token,json=jobToken,proto3" json:"job_token,omitempty"`
623+ // WorkflowPath is the path of the workflow file in the repository, for
624+ // example ".github/workflows/ci.yml".
625+ WorkflowPath string `protobuf:"bytes,5,opt,name=workflow_path,json=workflowPath,proto3" json:"workflow_path,omitempty"`
626+ // WorkflowYaml is the content of that file at sha, as is, at most 1 MiB
627+ // (larger files fail at planning).
628+ WorkflowYaml string `protobuf:"bytes,6,opt,name=workflow_yaml,json=workflowYaml,proto3" json:"workflow_yaml,omitempty"`
629+ // JobKey is the key of the job under "jobs:" in the workflow, the
630+ // "github.job".
631+ JobKey string `protobuf:"bytes,7,opt,name=job_key,json=jobKey,proto3" json:"job_key,omitempty"`
632+ // Matrix is the JSON object of the matrix values of this instance of the
633+ // job, for example {"os":"linux","go":"1.27"}. "{}" when the job has no
634+ // matrix. It is the "matrix" context.
635+ Matrix string `protobuf:"bytes,8,opt,name=matrix,proto3" json:"matrix,omitempty"`
636+ // Contexts is a JSON object with the contexts that the server computed for
637+ // this job: "github", "needs", "strategy", "matrix", "inputs" and "vars", in
638+ // the shape GitHub gives them. The contexts "job", "runner", "steps" and
639+ // "env" belong to the runner and are not in it. Other keys may be added
640+ // later; clients ignore those they do not know.
641+ Contexts string `protobuf:"bytes,9,opt,name=contexts,proto3" json:"contexts,omitempty"`
642+ // Labels are the runs-on labels of the job, evaluated.
643+ Labels []string `protobuf:"bytes,10,rep,name=labels,proto3" json:"labels,omitempty"`
644+ // TimeoutMinutes is the timeout of the job in minutes: the value of the
645+ // workflow's timeout-minutes, or 360 when it has none. At least 1. The
646+ // runner enforces it from the start of the job; the server ends a job that
647+ // exceeds it by announcing CANCEL_REASON_TIMEOUT.
648+ TimeoutMinutes int32 `protobuf:"varint,11,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"`
649+ // Repo is the name of the repository.
650+ Repo string `protobuf:"bytes,12,opt,name=repo,proto3" json:"repo,omitempty"`
651+ // Sha is the full id of the commit the run is for, 40 lowercase
652+ // hexadecimal characters for SHA-1. The runner reads the code at it.
653+ Sha string `protobuf:"bytes,13,opt,name=sha,proto3" json:"sha,omitempty"`
654+ // Ref is the full ref name that triggered the run, for example
655+ // "refs/heads/main" or "refs/tags/v1.0.0".
656+ Ref string `protobuf:"bytes,14,opt,name=ref,proto3" json:"ref,omitempty"`
657+ unknownFields protoimpl.UnknownFields
658+ sizeCache protoimpl.SizeCache
659+}
660+
661+func (x *Job) Reset() {
662+ *x = Job{}
663+ mi := &file_ermite_runner_proto_msgTypes[4]
664+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
665+ ms.StoreMessageInfo(mi)
666+}
667+
668+func (x *Job) String() string {
669+ return protoimpl.X.MessageStringOf(x)
670+}
671+
672+func (*Job) ProtoMessage() {}
673+
674+func (x *Job) ProtoReflect() protoreflect.Message {
675+ mi := &file_ermite_runner_proto_msgTypes[4]
676+ if x != nil {
677+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
678+ if ms.LoadMessageInfo() == nil {
679+ ms.StoreMessageInfo(mi)
680+ }
681+ return ms
682+ }
683+ return mi.MessageOf(x)
684+}
685+
686+// Deprecated: Use Job.ProtoReflect.Descriptor instead.
687+func (*Job) Descriptor() ([]byte, []int) {
688+ return file_ermite_runner_proto_rawDescGZIP(), []int{4}
689+}
690+
691+func (x *Job) GetJobId() string {
692+ if x != nil {
693+ return x.JobId
694+ }
695+ return ""
696+}
697+
698+func (x *Job) GetRunId() int64 {
699+ if x != nil {
700+ return x.RunId
701+ }
702+ return 0
703+}
704+
705+func (x *Job) GetAttempt() int32 {
706+ if x != nil {
707+ return x.Attempt
708+ }
709+ return 0
710+}
711+
712+func (x *Job) GetJobToken() string {
713+ if x != nil {
714+ return x.JobToken
715+ }
716+ return ""
717+}
718+
719+func (x *Job) GetWorkflowPath() string {
720+ if x != nil {
721+ return x.WorkflowPath
722+ }
723+ return ""
724+}
725+
726+func (x *Job) GetWorkflowYaml() string {
727+ if x != nil {
728+ return x.WorkflowYaml
729+ }
730+ return ""
731+}
732+
733+func (x *Job) GetJobKey() string {
734+ if x != nil {
735+ return x.JobKey
736+ }
737+ return ""
738+}
739+
740+func (x *Job) GetMatrix() string {
741+ if x != nil {
742+ return x.Matrix
743+ }
744+ return ""
745+}
746+
747+func (x *Job) GetContexts() string {
748+ if x != nil {
749+ return x.Contexts
750+ }
751+ return ""
752+}
753+
754+func (x *Job) GetLabels() []string {
755+ if x != nil {
756+ return x.Labels
757+ }
758+ return nil
759+}
760+
761+func (x *Job) GetTimeoutMinutes() int32 {
762+ if x != nil {
763+ return x.TimeoutMinutes
764+ }
765+ return 0
766+}
767+
768+func (x *Job) GetRepo() string {
769+ if x != nil {
770+ return x.Repo
771+ }
772+ return ""
773+}
774+
775+func (x *Job) GetSha() string {
776+ if x != nil {
777+ return x.Sha
778+ }
779+ return ""
780+}
781+
782+func (x *Job) GetRef() string {
783+ if x != nil {
784+ return x.Ref
785+ }
786+ return ""
787+}
788+
789+// UpdateJobRequest reports the steps and, in the last call, the end of a job.
790+type UpdateJobRequest struct {
791+ state protoimpl.MessageState `protogen:"open.v1"`
792+ // JobId is the Job.job_id. It must be the job of the token.
793+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
794+ // Steps are the steps whose state changed or that are new. Each one is a
795+ // full snapshot of its step, applied by number. At most 1,000 steps per job
796+ // (invalid_argument beyond). Steps are numbered like GitHub's log: the
797+ // implicit "Set up job" is 1, the steps of the workflow follow in order, and
798+ // the implicit "Complete job" is last.
799+ Steps []*StepUpdate `protobuf:"bytes,2,rep,name=steps,proto3" json:"steps,omitempty"`
800+ // Outputs are the outputs of the job, evaluated by the runner. Only allowed
801+ // in the call that carries the conclusion (invalid_argument otherwise).
802+ // Names are 1 to 255 characters among letters, digits, "_" and "-" and
803+ // begin with a letter or "_" and are case-insensitive: two names that
804+ // differ only by case are invalid_argument. Each value is at most 1 MiB, and
805+ // all names and values together at most 1 MiB (1,048,576 bytes); beyond that
806+ // the whole call is rejected with invalid_argument and the runner reports
807+ // the failure of the job without outputs, with a line in the log.
808+ Outputs map[string]string `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
809+ // Conclusion ends the job when set. A runner reports CONCLUSION_SUCCESS,
810+ // CONCLUSION_FAILURE, CONCLUSION_CANCELLED or CONCLUSION_TIMED_OUT; any
811+ // other value is invalid_argument. CONCLUSION_UNSPECIFIED means the job goes
812+ // on. Steps that were not reported COMPLETED when the job ends are
813+ // completed by the server as CONCLUSION_CANCELLED (or SKIPPED when they
814+ // never started and the job did not fail by cancellation).
815+ Conclusion Conclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.Conclusion" json:"conclusion,omitempty"`
816+ // RunnerShutdown is true when the runner ends the job because it was told to
817+ // stop (service stopped, signal). Only allowed with CONCLUSION_CANCELLED.
818+ // The server then annotates the job with GitHub's message "The runner has
819+ // received a shutdown signal. This can happen when the runner service is
820+ // stopped, or a manually started runner is canceled."
821+ RunnerShutdown bool `protobuf:"varint,5,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"`
822+ unknownFields protoimpl.UnknownFields
823+ sizeCache protoimpl.SizeCache
824+}
825+
826+func (x *UpdateJobRequest) Reset() {
827+ *x = UpdateJobRequest{}
828+ mi := &file_ermite_runner_proto_msgTypes[5]
829+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
830+ ms.StoreMessageInfo(mi)
831+}
832+
833+func (x *UpdateJobRequest) String() string {
834+ return protoimpl.X.MessageStringOf(x)
835+}
836+
837+func (*UpdateJobRequest) ProtoMessage() {}
838+
839+func (x *UpdateJobRequest) ProtoReflect() protoreflect.Message {
840+ mi := &file_ermite_runner_proto_msgTypes[5]
841+ if x != nil {
842+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
843+ if ms.LoadMessageInfo() == nil {
844+ ms.StoreMessageInfo(mi)
845+ }
846+ return ms
847+ }
848+ return mi.MessageOf(x)
849+}
850+
851+// Deprecated: Use UpdateJobRequest.ProtoReflect.Descriptor instead.
852+func (*UpdateJobRequest) Descriptor() ([]byte, []int) {
853+ return file_ermite_runner_proto_rawDescGZIP(), []int{5}
854+}
855+
856+func (x *UpdateJobRequest) GetJobId() string {
857+ if x != nil {
858+ return x.JobId
859+ }
860+ return ""
861+}
862+
863+func (x *UpdateJobRequest) GetSteps() []*StepUpdate {
864+ if x != nil {
865+ return x.Steps
866+ }
867+ return nil
868+}
869+
870+func (x *UpdateJobRequest) GetOutputs() map[string]string {
871+ if x != nil {
872+ return x.Outputs
873+ }
874+ return nil
875+}
876+
877+func (x *UpdateJobRequest) GetConclusion() Conclusion {
878+ if x != nil {
879+ return x.Conclusion
880+ }
881+ return Conclusion_CONCLUSION_UNSPECIFIED
882+}
883+
884+func (x *UpdateJobRequest) GetRunnerShutdown() bool {
885+ if x != nil {
886+ return x.RunnerShutdown
887+ }
888+ return false
889+}
890+
891+// StepUpdate is the state of one step as the runner knows it.
892+type StepUpdate struct {
893+ state protoimpl.MessageState `protogen:"open.v1"`
894+ // Number is the 1-based number of the step in the job. It is the number
895+ // used by AppendLog.
896+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
897+ // Name is the display name of the step, evaluated: the "name" of the
898+ // workflow step, or "Run <first line of the script>", as GitHub shows it.
899+ // At most 1,024 characters; the server cuts longer names.
900+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
901+ // State is where the step is.
902+ State StepState `protobuf:"varint,3,opt,name=state,proto3,enum=ermite.StepState" json:"state,omitempty"`
903+ // Conclusion is how the step ended. Required (not UNSPECIFIED) when state is
904+ // COMPLETED, must be UNSPECIFIED otherwise. Allowed values: success,
905+ // failure, cancelled, skipped.
906+ Conclusion Conclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.Conclusion" json:"conclusion,omitempty"`
907+ // StartedAt is when the step started, by the clock of the runner. Required
908+ // from IN_PROGRESS on.
909+ StartedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"`
910+ // CompletedAt is when the step ended, by the clock of the runner. Required
911+ // when state is COMPLETED.
912+ CompletedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"`
913+ unknownFields protoimpl.UnknownFields
914+ sizeCache protoimpl.SizeCache
915+}
916+
917+func (x *StepUpdate) Reset() {
918+ *x = StepUpdate{}
919+ mi := &file_ermite_runner_proto_msgTypes[6]
920+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
921+ ms.StoreMessageInfo(mi)
922+}
923+
924+func (x *StepUpdate) String() string {
925+ return protoimpl.X.MessageStringOf(x)
926+}
927+
928+func (*StepUpdate) ProtoMessage() {}
929+
930+func (x *StepUpdate) ProtoReflect() protoreflect.Message {
931+ mi := &file_ermite_runner_proto_msgTypes[6]
932+ if x != nil {
933+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
934+ if ms.LoadMessageInfo() == nil {
935+ ms.StoreMessageInfo(mi)
936+ }
937+ return ms
938+ }
939+ return mi.MessageOf(x)
940+}
941+
942+// Deprecated: Use StepUpdate.ProtoReflect.Descriptor instead.
943+func (*StepUpdate) Descriptor() ([]byte, []int) {
944+ return file_ermite_runner_proto_rawDescGZIP(), []int{6}
945+}
946+
947+func (x *StepUpdate) GetNumber() int32 {
948+ if x != nil {
949+ return x.Number
950+ }
951+ return 0
952+}
953+
954+func (x *StepUpdate) GetName() string {
955+ if x != nil {
956+ return x.Name
957+ }
958+ return ""
959+}
960+
961+func (x *StepUpdate) GetState() StepState {
962+ if x != nil {
963+ return x.State
964+ }
965+ return StepState_STEP_STATE_UNSPECIFIED
966+}
967+
968+func (x *StepUpdate) GetConclusion() Conclusion {
969+ if x != nil {
970+ return x.Conclusion
971+ }
972+ return Conclusion_CONCLUSION_UNSPECIFIED
973+}
974+
975+func (x *StepUpdate) GetStartedAt() *timestamppb.Timestamp {
976+ if x != nil {
977+ return x.StartedAt
978+ }
979+ return nil
980+}
981+
982+func (x *StepUpdate) GetCompletedAt() *timestamppb.Timestamp {
983+ if x != nil {
984+ return x.CompletedAt
985+ }
986+ return nil
987+}
988+
989+// UpdateJobResponse is empty.
990+type UpdateJobResponse struct {
991+ state protoimpl.MessageState `protogen:"open.v1"`
992+ unknownFields protoimpl.UnknownFields
993+ sizeCache protoimpl.SizeCache
994+}
995+
996+func (x *UpdateJobResponse) Reset() {
997+ *x = UpdateJobResponse{}
998+ mi := &file_ermite_runner_proto_msgTypes[7]
999+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1000+ ms.StoreMessageInfo(mi)
1001+}
1002+
1003+func (x *UpdateJobResponse) String() string {
1004+ return protoimpl.X.MessageStringOf(x)
1005+}
1006+
1007+func (*UpdateJobResponse) ProtoMessage() {}
1008+
1009+func (x *UpdateJobResponse) ProtoReflect() protoreflect.Message {
1010+ mi := &file_ermite_runner_proto_msgTypes[7]
1011+ if x != nil {
1012+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1013+ if ms.LoadMessageInfo() == nil {
1014+ ms.StoreMessageInfo(mi)
1015+ }
1016+ return ms
1017+ }
1018+ return mi.MessageOf(x)
1019+}
1020+
1021+// Deprecated: Use UpdateJobResponse.ProtoReflect.Descriptor instead.
1022+func (*UpdateJobResponse) Descriptor() ([]byte, []int) {
1023+ return file_ermite_runner_proto_rawDescGZIP(), []int{7}
1024+}
1025+
1026+// AppendLogRequest carries log lines of one step.
1027+type AppendLogRequest struct {
1028+ state protoimpl.MessageState `protogen:"open.v1"`
1029+ // JobId is the Job.job_id. It must be the job of the token.
1030+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1031+ // Step is the number of the step the lines belong to (see
1032+ // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not
1033+ // announced yet is created by the first lines.
1034+ Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"`
1035+ // Offset is the number of the first line of the request in the log of the
1036+ // job, counted from 0 across all steps. See the rules on AppendLog.
1037+ Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`
1038+ // Lines are the lines to append, in order: at most 1,000, at most 64 KiB of
1039+ // text in total.
1040+ Lines []*LogLine `protobuf:"bytes,4,rep,name=lines,proto3" json:"lines,omitempty"`
1041+ unknownFields protoimpl.UnknownFields
1042+ sizeCache protoimpl.SizeCache
1043+}
1044+
1045+func (x *AppendLogRequest) Reset() {
1046+ *x = AppendLogRequest{}
1047+ mi := &file_ermite_runner_proto_msgTypes[8]
1048+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1049+ ms.StoreMessageInfo(mi)
1050+}
1051+
1052+func (x *AppendLogRequest) String() string {
1053+ return protoimpl.X.MessageStringOf(x)
1054+}
1055+
1056+func (*AppendLogRequest) ProtoMessage() {}
1057+
1058+func (x *AppendLogRequest) ProtoReflect() protoreflect.Message {
1059+ mi := &file_ermite_runner_proto_msgTypes[8]
1060+ if x != nil {
1061+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1062+ if ms.LoadMessageInfo() == nil {
1063+ ms.StoreMessageInfo(mi)
1064+ }
1065+ return ms
1066+ }
1067+ return mi.MessageOf(x)
1068+}
1069+
1070+// Deprecated: Use AppendLogRequest.ProtoReflect.Descriptor instead.
1071+func (*AppendLogRequest) Descriptor() ([]byte, []int) {
1072+ return file_ermite_runner_proto_rawDescGZIP(), []int{8}
1073+}
1074+
1075+func (x *AppendLogRequest) GetJobId() string {
1076+ if x != nil {
1077+ return x.JobId
1078+ }
1079+ return ""
1080+}
1081+
1082+func (x *AppendLogRequest) GetStep() int32 {
1083+ if x != nil {
1084+ return x.Step
1085+ }
1086+ return 0
1087+}
1088+
1089+func (x *AppendLogRequest) GetOffset() int64 {
1090+ if x != nil {
1091+ return x.Offset
1092+ }
1093+ return 0
1094+}
1095+
1096+func (x *AppendLogRequest) GetLines() []*LogLine {
1097+ if x != nil {
1098+ return x.Lines
1099+ }
1100+ return nil
1101+}
1102+
1103+// LogLine is one line of a job log.
1104+type LogLine struct {
1105+ state protoimpl.MessageState `protogen:"open.v1"`
1106+ // Time is when the line was produced, by the clock of the runner. The server
1107+ // keeps it with nanosecond precision (RFC 3339) and does not reorder lines.
1108+ Time *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=time,proto3" json:"time,omitempty"`
1109+ // Text is the line without its line terminator, valid UTF-8 (the client
1110+ // replaces invalid bytes with U+FFFD). It must not contain a line feed; a
1111+ // carriage return is kept. It is the line as GitHub's raw log has it,
1112+ // including "##[group]" and "##[endgroup]" markers and "##[error]" lines.
1113+ Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"`
1114+ unknownFields protoimpl.UnknownFields
1115+ sizeCache protoimpl.SizeCache
1116+}
1117+
1118+func (x *LogLine) Reset() {
1119+ *x = LogLine{}
1120+ mi := &file_ermite_runner_proto_msgTypes[9]
1121+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1122+ ms.StoreMessageInfo(mi)
1123+}
1124+
1125+func (x *LogLine) String() string {
1126+ return protoimpl.X.MessageStringOf(x)
1127+}
1128+
1129+func (*LogLine) ProtoMessage() {}
1130+
1131+func (x *LogLine) ProtoReflect() protoreflect.Message {
1132+ mi := &file_ermite_runner_proto_msgTypes[9]
1133+ if x != nil {
1134+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1135+ if ms.LoadMessageInfo() == nil {
1136+ ms.StoreMessageInfo(mi)
1137+ }
1138+ return ms
1139+ }
1140+ return mi.MessageOf(x)
1141+}
1142+
1143+// Deprecated: Use LogLine.ProtoReflect.Descriptor instead.
1144+func (*LogLine) Descriptor() ([]byte, []int) {
1145+ return file_ermite_runner_proto_rawDescGZIP(), []int{9}
1146+}
1147+
1148+func (x *LogLine) GetTime() *timestamppb.Timestamp {
1149+ if x != nil {
1150+ return x.Time
1151+ }
1152+ return nil
1153+}
1154+
1155+func (x *LogLine) GetText() string {
1156+ if x != nil {
1157+ return x.Text
1158+ }
1159+ return ""
1160+}
1161+
1162+// AppendLogResponse tells how much of the log the server holds.
1163+type AppendLogResponse struct {
1164+ state protoimpl.MessageState `protogen:"open.v1"`
1165+ // NextOffset is the number of lines of the job log after the call, which is
1166+ // the offset of the next request.
1167+ NextOffset int64 `protobuf:"varint,1,opt,name=next_offset,json=nextOffset,proto3" json:"next_offset,omitempty"`
1168+ unknownFields protoimpl.UnknownFields
1169+ sizeCache protoimpl.SizeCache
1170+}
1171+
1172+func (x *AppendLogResponse) Reset() {
1173+ *x = AppendLogResponse{}
1174+ mi := &file_ermite_runner_proto_msgTypes[10]
1175+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1176+ ms.StoreMessageInfo(mi)
1177+}
1178+
1179+func (x *AppendLogResponse) String() string {
1180+ return protoimpl.X.MessageStringOf(x)
1181+}
1182+
1183+func (*AppendLogResponse) ProtoMessage() {}
1184+
1185+func (x *AppendLogResponse) ProtoReflect() protoreflect.Message {
1186+ mi := &file_ermite_runner_proto_msgTypes[10]
1187+ if x != nil {
1188+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1189+ if ms.LoadMessageInfo() == nil {
1190+ ms.StoreMessageInfo(mi)
1191+ }
1192+ return ms
1193+ }
1194+ return mi.MessageOf(x)
1195+}
1196+
1197+// Deprecated: Use AppendLogResponse.ProtoReflect.Descriptor instead.
1198+func (*AppendLogResponse) Descriptor() ([]byte, []int) {
1199+ return file_ermite_runner_proto_rawDescGZIP(), []int{10}
1200+}
1201+
1202+func (x *AppendLogResponse) GetNextOffset() int64 {
1203+ if x != nil {
1204+ return x.NextOffset
1205+ }
1206+ return 0
1207+}
1208+
1209+// HeartbeatRequest names the job that is alive.
1210+type HeartbeatRequest struct {
1211+ state protoimpl.MessageState `protogen:"open.v1"`
1212+ // JobId is the Job.job_id. It must be the job of the token.
1213+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1214+ unknownFields protoimpl.UnknownFields
1215+ sizeCache protoimpl.SizeCache
1216+}
1217+
1218+func (x *HeartbeatRequest) Reset() {
1219+ *x = HeartbeatRequest{}
1220+ mi := &file_ermite_runner_proto_msgTypes[11]
1221+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1222+ ms.StoreMessageInfo(mi)
1223+}
1224+
1225+func (x *HeartbeatRequest) String() string {
1226+ return protoimpl.X.MessageStringOf(x)
1227+}
1228+
1229+func (*HeartbeatRequest) ProtoMessage() {}
1230+
1231+func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message {
1232+ mi := &file_ermite_runner_proto_msgTypes[11]
1233+ if x != nil {
1234+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1235+ if ms.LoadMessageInfo() == nil {
1236+ ms.StoreMessageInfo(mi)
1237+ }
1238+ return ms
1239+ }
1240+ return mi.MessageOf(x)
1241+}
1242+
1243+// Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead.
1244+func (*HeartbeatRequest) Descriptor() ([]byte, []int) {
1245+ return file_ermite_runner_proto_rawDescGZIP(), []int{11}
1246+}
1247+
1248+func (x *HeartbeatRequest) GetJobId() string {
1249+ if x != nil {
1250+ return x.JobId
1251+ }
1252+ return ""
1253+}
1254+
1255+// HeartbeatResponse is empty.
1256+type HeartbeatResponse struct {
1257+ state protoimpl.MessageState `protogen:"open.v1"`
1258+ unknownFields protoimpl.UnknownFields
1259+ sizeCache protoimpl.SizeCache
1260+}
1261+
1262+func (x *HeartbeatResponse) Reset() {
1263+ *x = HeartbeatResponse{}
1264+ mi := &file_ermite_runner_proto_msgTypes[12]
1265+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1266+ ms.StoreMessageInfo(mi)
1267+}
1268+
1269+func (x *HeartbeatResponse) String() string {
1270+ return protoimpl.X.MessageStringOf(x)
1271+}
1272+
1273+func (*HeartbeatResponse) ProtoMessage() {}
1274+
1275+func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message {
1276+ mi := &file_ermite_runner_proto_msgTypes[12]
1277+ if x != nil {
1278+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1279+ if ms.LoadMessageInfo() == nil {
1280+ ms.StoreMessageInfo(mi)
1281+ }
1282+ return ms
1283+ }
1284+ return mi.MessageOf(x)
1285+}
1286+
1287+// Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead.
1288+func (*HeartbeatResponse) Descriptor() ([]byte, []int) {
1289+ return file_ermite_runner_proto_rawDescGZIP(), []int{12}
1290+}
1291+
1292+// WatchJobRequest names the job to watch.
1293+type WatchJobRequest struct {
1294+ state protoimpl.MessageState `protogen:"open.v1"`
1295+ // JobId is the Job.job_id. It must be the job of the token.
1296+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1297+ unknownFields protoimpl.UnknownFields
1298+ sizeCache protoimpl.SizeCache
1299+}
1300+
1301+func (x *WatchJobRequest) Reset() {
1302+ *x = WatchJobRequest{}
1303+ mi := &file_ermite_runner_proto_msgTypes[13]
1304+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1305+ ms.StoreMessageInfo(mi)
1306+}
1307+
1308+func (x *WatchJobRequest) String() string {
1309+ return protoimpl.X.MessageStringOf(x)
1310+}
1311+
1312+func (*WatchJobRequest) ProtoMessage() {}
1313+
1314+func (x *WatchJobRequest) ProtoReflect() protoreflect.Message {
1315+ mi := &file_ermite_runner_proto_msgTypes[13]
1316+ if x != nil {
1317+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1318+ if ms.LoadMessageInfo() == nil {
1319+ ms.StoreMessageInfo(mi)
1320+ }
1321+ return ms
1322+ }
1323+ return mi.MessageOf(x)
1324+}
1325+
1326+// Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead.
1327+func (*WatchJobRequest) Descriptor() ([]byte, []int) {
1328+ return file_ermite_runner_proto_rawDescGZIP(), []int{13}
1329+}
1330+
1331+func (x *WatchJobRequest) GetJobId() string {
1332+ if x != nil {
1333+ return x.JobId
1334+ }
1335+ return ""
1336+}
1337+
1338+// WatchJobResponse is one message of the stream: a request to cancel the job,
1339+// or a keep-alive.
1340+type WatchJobResponse struct {
1341+ state protoimpl.MessageState `protogen:"open.v1"`
1342+ // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a
1343+ // keep-alive: the other fields are empty and the client ignores it. New
1344+ // reasons may be added; a runner that does not know one cancels the job and
1345+ // reports CONCLUSION_CANCELLED.
1346+ Reason CancelReason `protobuf:"varint,1,opt,name=reason,proto3,enum=ermite.CancelReason" json:"reason,omitempty"`
1347+ // Message is a short human-readable explanation, for the job log, for
1348+ // example "Canceled by the owner". At most 1,024 characters.
1349+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
1350+ // RequestedAt is when the cancellation was requested, by the clock of the
1351+ // server.
1352+ RequestedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=requested_at,json=requestedAt,proto3" json:"requested_at,omitempty"`
1353+ unknownFields protoimpl.UnknownFields
1354+ sizeCache protoimpl.SizeCache
1355+}
1356+
1357+func (x *WatchJobResponse) Reset() {
1358+ *x = WatchJobResponse{}
1359+ mi := &file_ermite_runner_proto_msgTypes[14]
1360+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1361+ ms.StoreMessageInfo(mi)
1362+}
1363+
1364+func (x *WatchJobResponse) String() string {
1365+ return protoimpl.X.MessageStringOf(x)
1366+}
1367+
1368+func (*WatchJobResponse) ProtoMessage() {}
1369+
1370+func (x *WatchJobResponse) ProtoReflect() protoreflect.Message {
1371+ mi := &file_ermite_runner_proto_msgTypes[14]
1372+ if x != nil {
1373+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1374+ if ms.LoadMessageInfo() == nil {
1375+ ms.StoreMessageInfo(mi)
1376+ }
1377+ return ms
1378+ }
1379+ return mi.MessageOf(x)
1380+}
1381+
1382+// Deprecated: Use WatchJobResponse.ProtoReflect.Descriptor instead.
1383+func (*WatchJobResponse) Descriptor() ([]byte, []int) {
1384+ return file_ermite_runner_proto_rawDescGZIP(), []int{14}
1385+}
1386+
1387+func (x *WatchJobResponse) GetReason() CancelReason {
1388+ if x != nil {
1389+ return x.Reason
1390+ }
1391+ return CancelReason_CANCEL_REASON_UNSPECIFIED
1392+}
1393+
1394+func (x *WatchJobResponse) GetMessage() string {
1395+ if x != nil {
1396+ return x.Message
1397+ }
1398+ return ""
1399+}
1400+
1401+func (x *WatchJobResponse) GetRequestedAt() *timestamppb.Timestamp {
1402+ if x != nil {
1403+ return x.RequestedAt
1404+ }
1405+ return nil
1406+}
1407+
1408+var File_ermite_runner_proto protoreflect.FileDescriptor
1409+
1410+const file_ermite_runner_proto_rawDesc = "" +
1411+ "\n" +
1412+ "\x13ermite/runner.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb8\x01\n" +
1413+ "\x15RegisterRunnerRequest\x12\x12\n" +
1414+ "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
1415+ "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x18\n" +
1416+ "\aversion\x18\x03 \x01(\tR\aversion\x125\n" +
1417+ "\tisolation\x18\x04 \x01(\x0e2\x17.ermite.RunnerIsolationR\tisolation\x12\x12\n" +
1418+ "\x04arch\x18\x05 \x01(\tR\x04arch\x12\x0e\n" +
1419+ "\x02os\x18\x06 \x01(\tR\x02os\"X\n" +
1420+ "\x16RegisterRunnerResponse\x12\x1b\n" +
1421+ "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" +
1422+ "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\")\n" +
1423+ "\x0fFetchJobRequest\x12\x16\n" +
1424+ "\x06labels\x18\x01 \x03(\tR\x06labels\"1\n" +
1425+ "\x10FetchJobResponse\x12\x1d\n" +
1426+ "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\"\xfa\x02\n" +
1427+ "\x03Job\x12\x15\n" +
1428+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" +
1429+ "\x06run_id\x18\x02 \x01(\x03R\x05runId\x12\x18\n" +
1430+ "\aattempt\x18\x03 \x01(\x05R\aattempt\x12\x1b\n" +
1431+ "\tjob_token\x18\x04 \x01(\tR\bjobToken\x12#\n" +
1432+ "\rworkflow_path\x18\x05 \x01(\tR\fworkflowPath\x12#\n" +
1433+ "\rworkflow_yaml\x18\x06 \x01(\tR\fworkflowYaml\x12\x17\n" +
1434+ "\ajob_key\x18\a \x01(\tR\x06jobKey\x12\x16\n" +
1435+ "\x06matrix\x18\b \x01(\tR\x06matrix\x12\x1a\n" +
1436+ "\bcontexts\x18\t \x01(\tR\bcontexts\x12\x16\n" +
1437+ "\x06labels\x18\n" +
1438+ " \x03(\tR\x06labels\x12'\n" +
1439+ "\x0ftimeout_minutes\x18\v \x01(\x05R\x0etimeoutMinutes\x12\x12\n" +
1440+ "\x04repo\x18\f \x01(\tR\x04repo\x12\x10\n" +
1441+ "\x03sha\x18\r \x01(\tR\x03sha\x12\x10\n" +
1442+ "\x03ref\x18\x0e \x01(\tR\x03ref\"\xad\x02\n" +
1443+ "\x10UpdateJobRequest\x12\x15\n" +
1444+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12(\n" +
1445+ "\x05steps\x18\x02 \x03(\v2\x12.ermite.StepUpdateR\x05steps\x12?\n" +
1446+ "\aoutputs\x18\x03 \x03(\v2%.ermite.UpdateJobRequest.OutputsEntryR\aoutputs\x122\n" +
1447+ "\n" +
1448+ "conclusion\x18\x04 \x01(\x0e2\x12.ermite.ConclusionR\n" +
1449+ "conclusion\x12'\n" +
1450+ "\x0frunner_shutdown\x18\x05 \x01(\bR\x0erunnerShutdown\x1a:\n" +
1451+ "\fOutputsEntry\x12\x10\n" +
1452+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1453+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8f\x02\n" +
1454+ "\n" +
1455+ "StepUpdate\x12\x16\n" +
1456+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1457+ "\x04name\x18\x02 \x01(\tR\x04name\x12'\n" +
1458+ "\x05state\x18\x03 \x01(\x0e2\x11.ermite.StepStateR\x05state\x122\n" +
1459+ "\n" +
1460+ "conclusion\x18\x04 \x01(\x0e2\x12.ermite.ConclusionR\n" +
1461+ "conclusion\x129\n" +
1462+ "\n" +
1463+ "started_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" +
1464+ "\fcompleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\"\x13\n" +
1465+ "\x11UpdateJobResponse\"|\n" +
1466+ "\x10AppendLogRequest\x12\x15\n" +
1467+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x12\n" +
1468+ "\x04step\x18\x02 \x01(\x05R\x04step\x12\x16\n" +
1469+ "\x06offset\x18\x03 \x01(\x03R\x06offset\x12%\n" +
1470+ "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"M\n" +
1471+ "\aLogLine\x12.\n" +
1472+ "\x04time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x12\x12\n" +
1473+ "\x04text\x18\x02 \x01(\tR\x04text\"4\n" +
1474+ "\x11AppendLogResponse\x12\x1f\n" +
1475+ "\vnext_offset\x18\x01 \x01(\x03R\n" +
1476+ "nextOffset\")\n" +
1477+ "\x10HeartbeatRequest\x12\x15\n" +
1478+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x13\n" +
1479+ "\x11HeartbeatResponse\"(\n" +
1480+ "\x0fWatchJobRequest\x12\x15\n" +
1481+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x99\x01\n" +
1482+ "\x10WatchJobResponse\x12,\n" +
1483+ "\x06reason\x18\x01 \x01(\x0e2\x14.ermite.CancelReasonR\x06reason\x12\x18\n" +
1484+ "\amessage\x18\x02 \x01(\tR\amessage\x12=\n" +
1485+ "\frequested_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt*t\n" +
1486+ "\tRunStatus\x12\x1a\n" +
1487+ "\x16RUN_STATUS_UNSPECIFIED\x10\x00\x12\x15\n" +
1488+ "\x11RUN_STATUS_QUEUED\x10\x01\x12\x1a\n" +
1489+ "\x16RUN_STATUS_IN_PROGRESS\x10\x02\x12\x18\n" +
1490+ "\x14RUN_STATUS_COMPLETED\x10\x03*\xdc\x01\n" +
1491+ "\n" +
1492+ "Conclusion\x12\x1a\n" +
1493+ "\x16CONCLUSION_UNSPECIFIED\x10\x00\x12\x16\n" +
1494+ "\x12CONCLUSION_SUCCESS\x10\x01\x12\x16\n" +
1495+ "\x12CONCLUSION_FAILURE\x10\x02\x12\x18\n" +
1496+ "\x14CONCLUSION_CANCELLED\x10\x03\x12\x16\n" +
1497+ "\x12CONCLUSION_SKIPPED\x10\x04\x12\x18\n" +
1498+ "\x14CONCLUSION_TIMED_OUT\x10\x05\x12\x16\n" +
1499+ "\x12CONCLUSION_NEUTRAL\x10\x06\x12\x1e\n" +
1500+ "\x1aCONCLUSION_ACTION_REQUIRED\x10\a*u\n" +
1501+ "\tStepState\x12\x1a\n" +
1502+ "\x16STEP_STATE_UNSPECIFIED\x10\x00\x12\x16\n" +
1503+ "\x12STEP_STATE_PENDING\x10\x01\x12\x1a\n" +
1504+ "\x16STEP_STATE_IN_PROGRESS\x10\x02\x12\x18\n" +
1505+ "\x14STEP_STATE_COMPLETED\x10\x03*\x9e\x01\n" +
1506+ "\x0fRunnerIsolation\x12 \n" +
1507+ "\x1cRUNNER_ISOLATION_UNSPECIFIED\x10\x00\x12\x17\n" +
1508+ "\x13RUNNER_ISOLATION_VM\x10\x01\x12%\n" +
1509+ "!RUNNER_ISOLATION_CONTAINER_SYSBOX\x10\x02\x12)\n" +
1510+ "%RUNNER_ISOLATION_CONTAINER_PRIVILEGED\x10\x03*}\n" +
1511+ "\fCancelReason\x12\x1d\n" +
1512+ "\x19CANCEL_REASON_UNSPECIFIED\x10\x00\x12\x16\n" +
1513+ "\x12CANCEL_REASON_USER\x10\x01\x12\x1b\n" +
1514+ "\x17CANCEL_REASON_FAIL_FAST\x10\x02\x12\x19\n" +
1515+ "\x15CANCEL_REASON_TIMEOUT\x10\x032\xa6\x03\n" +
1516+ "\rRunnerService\x12O\n" +
1517+ "\x0eRegisterRunner\x12\x1d.ermite.RegisterRunnerRequest\x1a\x1e.ermite.RegisterRunnerResponse\x12=\n" +
1518+ "\bFetchJob\x12\x17.ermite.FetchJobRequest\x1a\x18.ermite.FetchJobResponse\x12@\n" +
1519+ "\tUpdateJob\x12\x18.ermite.UpdateJobRequest\x1a\x19.ermite.UpdateJobResponse\x12@\n" +
1520+ "\tAppendLog\x12\x18.ermite.AppendLogRequest\x1a\x19.ermite.AppendLogResponse\x12@\n" +
1521+ "\tHeartbeat\x12\x18.ermite.HeartbeatRequest\x1a\x19.ermite.HeartbeatResponse\x12?\n" +
1522+ "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01Br\n" +
1523+ "\n" +
1524+ "com.ermiteB\vRunnerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
1525+
1526+var (
1527+ file_ermite_runner_proto_rawDescOnce sync.Once
1528+ file_ermite_runner_proto_rawDescData []byte
1529+)
1530+
1531+func file_ermite_runner_proto_rawDescGZIP() []byte {
1532+ file_ermite_runner_proto_rawDescOnce.Do(func() {
1533+ file_ermite_runner_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)))
1534+ })
1535+ return file_ermite_runner_proto_rawDescData
1536+}
1537+
1538+var file_ermite_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
1539+var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
1540+var file_ermite_runner_proto_goTypes = []any{
1541+ (RunStatus)(0), // 0: ermite.RunStatus
1542+ (Conclusion)(0), // 1: ermite.Conclusion
1543+ (StepState)(0), // 2: ermite.StepState
1544+ (RunnerIsolation)(0), // 3: ermite.RunnerIsolation
1545+ (CancelReason)(0), // 4: ermite.CancelReason
1546+ (*RegisterRunnerRequest)(nil), // 5: ermite.RegisterRunnerRequest
1547+ (*RegisterRunnerResponse)(nil), // 6: ermite.RegisterRunnerResponse
1548+ (*FetchJobRequest)(nil), // 7: ermite.FetchJobRequest
1549+ (*FetchJobResponse)(nil), // 8: ermite.FetchJobResponse
1550+ (*Job)(nil), // 9: ermite.Job
1551+ (*UpdateJobRequest)(nil), // 10: ermite.UpdateJobRequest
1552+ (*StepUpdate)(nil), // 11: ermite.StepUpdate
1553+ (*UpdateJobResponse)(nil), // 12: ermite.UpdateJobResponse
1554+ (*AppendLogRequest)(nil), // 13: ermite.AppendLogRequest
1555+ (*LogLine)(nil), // 14: ermite.LogLine
1556+ (*AppendLogResponse)(nil), // 15: ermite.AppendLogResponse
1557+ (*HeartbeatRequest)(nil), // 16: ermite.HeartbeatRequest
1558+ (*HeartbeatResponse)(nil), // 17: ermite.HeartbeatResponse
1559+ (*WatchJobRequest)(nil), // 18: ermite.WatchJobRequest
1560+ (*WatchJobResponse)(nil), // 19: ermite.WatchJobResponse
1561+ nil, // 20: ermite.UpdateJobRequest.OutputsEntry
1562+ (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp
1563+}
1564+var file_ermite_runner_proto_depIdxs = []int32{
1565+ 3, // 0: ermite.RegisterRunnerRequest.isolation:type_name -> ermite.RunnerIsolation
1566+ 9, // 1: ermite.FetchJobResponse.job:type_name -> ermite.Job
1567+ 11, // 2: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate
1568+ 20, // 3: ermite.UpdateJobRequest.outputs:type_name -> ermite.UpdateJobRequest.OutputsEntry
1569+ 1, // 4: ermite.UpdateJobRequest.conclusion:type_name -> ermite.Conclusion
1570+ 2, // 5: ermite.StepUpdate.state:type_name -> ermite.StepState
1571+ 1, // 6: ermite.StepUpdate.conclusion:type_name -> ermite.Conclusion
1572+ 21, // 7: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp
1573+ 21, // 8: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp
1574+ 14, // 9: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine
1575+ 21, // 10: ermite.LogLine.time:type_name -> google.protobuf.Timestamp
1576+ 4, // 11: ermite.WatchJobResponse.reason:type_name -> ermite.CancelReason
1577+ 21, // 12: ermite.WatchJobResponse.requested_at:type_name -> google.protobuf.Timestamp
1578+ 5, // 13: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest
1579+ 7, // 14: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest
1580+ 10, // 15: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest
1581+ 13, // 16: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest
1582+ 16, // 17: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest
1583+ 18, // 18: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest
1584+ 6, // 19: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse
1585+ 8, // 20: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse
1586+ 12, // 21: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse
1587+ 15, // 22: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse
1588+ 17, // 23: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse
1589+ 19, // 24: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse
1590+ 19, // [19:25] is the sub-list for method output_type
1591+ 13, // [13:19] is the sub-list for method input_type
1592+ 13, // [13:13] is the sub-list for extension type_name
1593+ 13, // [13:13] is the sub-list for extension extendee
1594+ 0, // [0:13] is the sub-list for field type_name
1595+}
1596+
1597+func init() { file_ermite_runner_proto_init() }
1598+func file_ermite_runner_proto_init() {
1599+ if File_ermite_runner_proto != nil {
1600+ return
1601+ }
1602+ type x struct{}
1603+ out := protoimpl.TypeBuilder{
1604+ File: protoimpl.DescBuilder{
1605+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1606+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)),
1607+ NumEnums: 5,
1608+ NumMessages: 16,
1609+ NumExtensions: 0,
1610+ NumServices: 1,
1611+ },
1612+ GoTypes: file_ermite_runner_proto_goTypes,
1613+ DependencyIndexes: file_ermite_runner_proto_depIdxs,
1614+ EnumInfos: file_ermite_runner_proto_enumTypes,
1615+ MessageInfos: file_ermite_runner_proto_msgTypes,
1616+ }.Build()
1617+ File_ermite_runner_proto = out.File
1618+ file_ermite_runner_proto_goTypes = nil
1619+ file_ermite_runner_proto_depIdxs = nil
1620+}
@@ -0,0 +1,1620 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-go. DO NOT EDIT.
4+// versions:
5+// protoc-gen-go v1.36.12
6+// protoc (unknown)
7+// source: ermite/runner.proto
8+
9+package ermite
10+
11+import (
12+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
13+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
14+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
15+ reflect "reflect"
16+ sync "sync"
17+ unsafe "unsafe"
18+)
19+
20+const (
21+ // Verify that this generated code is sufficiently up-to-date.
22+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
23+ // Verify that runtime/protoimpl is sufficiently up-to-date.
24+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
25+)
26+
27+// RunStatus is where a run, a job or a check is in its life. It is the same
28+// as GitHub's status, without the rarely used values.
29+type RunStatus int32
30+
31+const (
32+ // Not set. In a filter it means every status. Never returned by the server.
33+ RunStatus_RUN_STATUS_UNSPECIFIED RunStatus = 0
34+ // Waiting: for a runner, for the jobs it needs, or for its turn
35+ // (max-parallel).
36+ RunStatus_RUN_STATUS_QUEUED RunStatus = 1
37+ // At least one job has been given to a runner and has not ended.
38+ RunStatus_RUN_STATUS_IN_PROGRESS RunStatus = 2
39+ // Over. The conclusion says how it ended.
40+ RunStatus_RUN_STATUS_COMPLETED RunStatus = 3
41+)
42+
43+// Enum value maps for RunStatus.
44+var (
45+ RunStatus_name = map[int32]string{
46+ 0: "RUN_STATUS_UNSPECIFIED",
47+ 1: "RUN_STATUS_QUEUED",
48+ 2: "RUN_STATUS_IN_PROGRESS",
49+ 3: "RUN_STATUS_COMPLETED",
50+ }
51+ RunStatus_value = map[string]int32{
52+ "RUN_STATUS_UNSPECIFIED": 0,
53+ "RUN_STATUS_QUEUED": 1,
54+ "RUN_STATUS_IN_PROGRESS": 2,
55+ "RUN_STATUS_COMPLETED": 3,
56+ }
57+)
58+
59+func (x RunStatus) Enum() *RunStatus {
60+ p := new(RunStatus)
61+ *p = x
62+ return p
63+}
64+
65+func (x RunStatus) String() string {
66+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
67+}
68+
69+func (RunStatus) Descriptor() protoreflect.EnumDescriptor {
70+ return file_ermite_runner_proto_enumTypes[0].Descriptor()
71+}
72+
73+func (RunStatus) Type() protoreflect.EnumType {
74+ return &file_ermite_runner_proto_enumTypes[0]
75+}
76+
77+func (x RunStatus) Number() protoreflect.EnumNumber {
78+ return protoreflect.EnumNumber(x)
79+}
80+
81+// Deprecated: Use RunStatus.Descriptor instead.
82+func (RunStatus) EnumDescriptor() ([]byte, []int) {
83+ return file_ermite_runner_proto_rawDescGZIP(), []int{0}
84+}
85+
86+// Conclusion is how a run, a job, a step or a check ended, with GitHub's
87+// values and meaning. It is unset (CONCLUSION_UNSPECIFIED) until the status is
88+// COMPLETED.
89+type Conclusion int32
90+
91+const (
92+ // Not completed yet. Never returned by the server for a completed item.
93+ Conclusion_CONCLUSION_UNSPECIFIED Conclusion = 0
94+ // Everything it ran succeeded.
95+ Conclusion_CONCLUSION_SUCCESS Conclusion = 1
96+ // A step failed, or the job failed for another reason described in
97+ // RunJob.error (runner lost, queue timeout, log limit, feature not
98+ // supported yet, invalid workflow file).
99+ Conclusion_CONCLUSION_FAILURE Conclusion = 2
100+ // Cancelled by the owner, or by fail-fast, or because the runner shut down.
101+ Conclusion_CONCLUSION_CANCELLED Conclusion = 3
102+ // Never ran: its "if" was false, or a job it needs did not succeed, or it was
103+ // not selected. Never reported by a runner.
104+ Conclusion_CONCLUSION_SKIPPED Conclusion = 4
105+ // Exceeded its timeout.
106+ Conclusion_CONCLUSION_TIMED_OUT Conclusion = 5
107+ // Neither a success nor a failure. Defined for parity with GitHub; no
108+ // workflow job ends this way yet. For merging it counts as a success.
109+ Conclusion_CONCLUSION_NEUTRAL Conclusion = 6
110+ // Waits for a human action (for example the approval of an environment).
111+ // Defined for parity with GitHub; the server does not produce it before
112+ // environments exist. For merging it counts as a failure.
113+ Conclusion_CONCLUSION_ACTION_REQUIRED Conclusion = 7
114+)
115+
116+// Enum value maps for Conclusion.
117+var (
118+ Conclusion_name = map[int32]string{
119+ 0: "CONCLUSION_UNSPECIFIED",
120+ 1: "CONCLUSION_SUCCESS",
121+ 2: "CONCLUSION_FAILURE",
122+ 3: "CONCLUSION_CANCELLED",
123+ 4: "CONCLUSION_SKIPPED",
124+ 5: "CONCLUSION_TIMED_OUT",
125+ 6: "CONCLUSION_NEUTRAL",
126+ 7: "CONCLUSION_ACTION_REQUIRED",
127+ }
128+ Conclusion_value = map[string]int32{
129+ "CONCLUSION_UNSPECIFIED": 0,
130+ "CONCLUSION_SUCCESS": 1,
131+ "CONCLUSION_FAILURE": 2,
132+ "CONCLUSION_CANCELLED": 3,
133+ "CONCLUSION_SKIPPED": 4,
134+ "CONCLUSION_TIMED_OUT": 5,
135+ "CONCLUSION_NEUTRAL": 6,
136+ "CONCLUSION_ACTION_REQUIRED": 7,
137+ }
138+)
139+
140+func (x Conclusion) Enum() *Conclusion {
141+ p := new(Conclusion)
142+ *p = x
143+ return p
144+}
145+
146+func (x Conclusion) String() string {
147+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
148+}
149+
150+func (Conclusion) Descriptor() protoreflect.EnumDescriptor {
151+ return file_ermite_runner_proto_enumTypes[1].Descriptor()
152+}
153+
154+func (Conclusion) Type() protoreflect.EnumType {
155+ return &file_ermite_runner_proto_enumTypes[1]
156+}
157+
158+func (x Conclusion) Number() protoreflect.EnumNumber {
159+ return protoreflect.EnumNumber(x)
160+}
161+
162+// Deprecated: Use Conclusion.Descriptor instead.
163+func (Conclusion) EnumDescriptor() ([]byte, []int) {
164+ return file_ermite_runner_proto_rawDescGZIP(), []int{1}
165+}
166+
167+// StepState is where a step is in its life.
168+type StepState int32
169+
170+const (
171+ // Not set. Rejected in requests, never returned by the server.
172+ StepState_STEP_STATE_UNSPECIFIED StepState = 0
173+ // Known but not started.
174+ StepState_STEP_STATE_PENDING StepState = 1
175+ // Running.
176+ StepState_STEP_STATE_IN_PROGRESS StepState = 2
177+ // Over. The conclusion says how it ended. A step that did not run because
178+ // of its "if" or an earlier failure is COMPLETED with CONCLUSION_SKIPPED.
179+ StepState_STEP_STATE_COMPLETED StepState = 3
180+)
181+
182+// Enum value maps for StepState.
183+var (
184+ StepState_name = map[int32]string{
185+ 0: "STEP_STATE_UNSPECIFIED",
186+ 1: "STEP_STATE_PENDING",
187+ 2: "STEP_STATE_IN_PROGRESS",
188+ 3: "STEP_STATE_COMPLETED",
189+ }
190+ StepState_value = map[string]int32{
191+ "STEP_STATE_UNSPECIFIED": 0,
192+ "STEP_STATE_PENDING": 1,
193+ "STEP_STATE_IN_PROGRESS": 2,
194+ "STEP_STATE_COMPLETED": 3,
195+ }
196+)
197+
198+func (x StepState) Enum() *StepState {
199+ p := new(StepState)
200+ *p = x
201+ return p
202+}
203+
204+func (x StepState) String() string {
205+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
206+}
207+
208+func (StepState) Descriptor() protoreflect.EnumDescriptor {
209+ return file_ermite_runner_proto_enumTypes[2].Descriptor()
210+}
211+
212+func (StepState) Type() protoreflect.EnumType {
213+ return &file_ermite_runner_proto_enumTypes[2]
214+}
215+
216+func (x StepState) Number() protoreflect.EnumNumber {
217+ return protoreflect.EnumNumber(x)
218+}
219+
220+// Deprecated: Use StepState.Descriptor instead.
221+func (StepState) EnumDescriptor() ([]byte, []int) {
222+ return file_ermite_runner_proto_rawDescGZIP(), []int{2}
223+}
224+
225+// RunnerIsolation is how a runner isolates each job from the host and from
226+// other jobs. The runner chooses it (auto-detection included) and reports it
227+// at registration; the job page shows it.
228+type RunnerIsolation int32
229+
230+const (
231+ // Not set. Rejected at registration, never returned by the server.
232+ RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED RunnerIsolation = 0
233+ // One micro-VM per job, destroyed at the end.
234+ RunnerIsolation_RUNNER_ISOLATION_VM RunnerIsolation = 1
235+ // One container per job with its own Docker daemon, run by the sysbox
236+ // runtime. Shares the host kernel.
237+ RunnerIsolation_RUNNER_ISOLATION_CONTAINER_SYSBOX RunnerIsolation = 2
238+ // One privileged container per job with its own Docker daemon. Shares the
239+ // host kernel and is equivalent to root on the host: the weakest mode.
240+ RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED RunnerIsolation = 3
241+)
242+
243+// Enum value maps for RunnerIsolation.
244+var (
245+ RunnerIsolation_name = map[int32]string{
246+ 0: "RUNNER_ISOLATION_UNSPECIFIED",
247+ 1: "RUNNER_ISOLATION_VM",
248+ 2: "RUNNER_ISOLATION_CONTAINER_SYSBOX",
249+ 3: "RUNNER_ISOLATION_CONTAINER_PRIVILEGED",
250+ }
251+ RunnerIsolation_value = map[string]int32{
252+ "RUNNER_ISOLATION_UNSPECIFIED": 0,
253+ "RUNNER_ISOLATION_VM": 1,
254+ "RUNNER_ISOLATION_CONTAINER_SYSBOX": 2,
255+ "RUNNER_ISOLATION_CONTAINER_PRIVILEGED": 3,
256+ }
257+)
258+
259+func (x RunnerIsolation) Enum() *RunnerIsolation {
260+ p := new(RunnerIsolation)
261+ *p = x
262+ return p
263+}
264+
265+func (x RunnerIsolation) String() string {
266+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
267+}
268+
269+func (RunnerIsolation) Descriptor() protoreflect.EnumDescriptor {
270+ return file_ermite_runner_proto_enumTypes[3].Descriptor()
271+}
272+
273+func (RunnerIsolation) Type() protoreflect.EnumType {
274+ return &file_ermite_runner_proto_enumTypes[3]
275+}
276+
277+func (x RunnerIsolation) Number() protoreflect.EnumNumber {
278+ return protoreflect.EnumNumber(x)
279+}
280+
281+// Deprecated: Use RunnerIsolation.Descriptor instead.
282+func (RunnerIsolation) EnumDescriptor() ([]byte, []int) {
283+ return file_ermite_runner_proto_rawDescGZIP(), []int{3}
284+}
285+
286+// CancelReason says why the server asks a runner to stop a job.
287+type CancelReason int32
288+
289+const (
290+ // Not set. A WatchJobResponse with this reason is a keep-alive and carries
291+ // no request.
292+ CancelReason_CANCEL_REASON_UNSPECIFIED CancelReason = 0
293+ // The owner cancelled the run (RunService.CancelRun, web or CLI). The job
294+ // ends CONCLUSION_CANCELLED.
295+ CancelReason_CANCEL_REASON_USER CancelReason = 1
296+ // Another job of the same matrix failed and the strategy has fail-fast. The
297+ // job ends CONCLUSION_CANCELLED.
298+ CancelReason_CANCEL_REASON_FAIL_FAST CancelReason = 2
299+ // The job exceeded its timeout_minutes without the runner ending it. The
300+ // job ends CONCLUSION_TIMED_OUT.
301+ CancelReason_CANCEL_REASON_TIMEOUT CancelReason = 3
302+)
303+
304+// Enum value maps for CancelReason.
305+var (
306+ CancelReason_name = map[int32]string{
307+ 0: "CANCEL_REASON_UNSPECIFIED",
308+ 1: "CANCEL_REASON_USER",
309+ 2: "CANCEL_REASON_FAIL_FAST",
310+ 3: "CANCEL_REASON_TIMEOUT",
311+ }
312+ CancelReason_value = map[string]int32{
313+ "CANCEL_REASON_UNSPECIFIED": 0,
314+ "CANCEL_REASON_USER": 1,
315+ "CANCEL_REASON_FAIL_FAST": 2,
316+ "CANCEL_REASON_TIMEOUT": 3,
317+ }
318+)
319+
320+func (x CancelReason) Enum() *CancelReason {
321+ p := new(CancelReason)
322+ *p = x
323+ return p
324+}
325+
326+func (x CancelReason) String() string {
327+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
328+}
329+
330+func (CancelReason) Descriptor() protoreflect.EnumDescriptor {
331+ return file_ermite_runner_proto_enumTypes[4].Descriptor()
332+}
333+
334+func (CancelReason) Type() protoreflect.EnumType {
335+ return &file_ermite_runner_proto_enumTypes[4]
336+}
337+
338+func (x CancelReason) Number() protoreflect.EnumNumber {
339+ return protoreflect.EnumNumber(x)
340+}
341+
342+// Deprecated: Use CancelReason.Descriptor instead.
343+func (CancelReason) EnumDescriptor() ([]byte, []int) {
344+ return file_ermite_runner_proto_rawDescGZIP(), []int{4}
345+}
346+
347+// RegisterRunnerRequest describes the runner to register.
348+type RegisterRunnerRequest struct {
349+ state protoimpl.MessageState `protogen:"open.v1"`
350+ // Name identifies the runner for people (job page, logs, ListRunners) and is
351+ // unique: 1 to 64 characters among letters, digits, ".", "_" and "-". It is
352+ // not a label and never matches runs-on.
353+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
354+ // Labels are the labels the runner adds to its implicit ones: at most 64,
355+ // each 1 to 64 characters among letters, digits, ".", "_" and "-", for
356+ // example "ubuntu-latest". Comparison is case-insensitive and duplicates
357+ // are removed. The server adds the implicit labels "self-hosted", the os
358+ // and the arch ("linux", "x64" or "arm64"); the runner name is not a label.
359+ Labels []string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"`
360+ // Version is the release of the runner, for example "v0.1.0", or "devel".
361+ // Informational, at most 64 characters.
362+ Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
363+ // Isolation is how the runner isolates jobs. Required.
364+ Isolation RunnerIsolation `protobuf:"varint,4,opt,name=isolation,proto3,enum=ermite.RunnerIsolation" json:"isolation,omitempty"`
365+ // Arch is the CPU architecture, in GitHub's names: "x64" or "arm64". Other
366+ // values are invalid_argument (the set may grow with compatible additions).
367+ Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"`
368+ // Os is the operating system: "linux". Other values are invalid_argument
369+ // (the set may grow with compatible additions).
370+ Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"`
371+ unknownFields protoimpl.UnknownFields
372+ sizeCache protoimpl.SizeCache
373+}
374+
375+func (x *RegisterRunnerRequest) Reset() {
376+ *x = RegisterRunnerRequest{}
377+ mi := &file_ermite_runner_proto_msgTypes[0]
378+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
379+ ms.StoreMessageInfo(mi)
380+}
381+
382+func (x *RegisterRunnerRequest) String() string {
383+ return protoimpl.X.MessageStringOf(x)
384+}
385+
386+func (*RegisterRunnerRequest) ProtoMessage() {}
387+
388+func (x *RegisterRunnerRequest) ProtoReflect() protoreflect.Message {
389+ mi := &file_ermite_runner_proto_msgTypes[0]
390+ if x != nil {
391+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
392+ if ms.LoadMessageInfo() == nil {
393+ ms.StoreMessageInfo(mi)
394+ }
395+ return ms
396+ }
397+ return mi.MessageOf(x)
398+}
399+
400+// Deprecated: Use RegisterRunnerRequest.ProtoReflect.Descriptor instead.
401+func (*RegisterRunnerRequest) Descriptor() ([]byte, []int) {
402+ return file_ermite_runner_proto_rawDescGZIP(), []int{0}
403+}
404+
405+func (x *RegisterRunnerRequest) GetName() string {
406+ if x != nil {
407+ return x.Name
408+ }
409+ return ""
410+}
411+
412+func (x *RegisterRunnerRequest) GetLabels() []string {
413+ if x != nil {
414+ return x.Labels
415+ }
416+ return nil
417+}
418+
419+func (x *RegisterRunnerRequest) GetVersion() string {
420+ if x != nil {
421+ return x.Version
422+ }
423+ return ""
424+}
425+
426+func (x *RegisterRunnerRequest) GetIsolation() RunnerIsolation {
427+ if x != nil {
428+ return x.Isolation
429+ }
430+ return RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED
431+}
432+
433+func (x *RegisterRunnerRequest) GetArch() string {
434+ if x != nil {
435+ return x.Arch
436+ }
437+ return ""
438+}
439+
440+func (x *RegisterRunnerRequest) GetOs() string {
441+ if x != nil {
442+ return x.Os
443+ }
444+ return ""
445+}
446+
447+// RegisterRunnerResponse holds the new runner and its secret.
448+type RegisterRunnerResponse struct {
449+ state protoimpl.MessageState `protogen:"open.v1"`
450+ // RunnerId identifies the runner in ListRunners and RemoveRunner. It is not
451+ // a secret.
452+ RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"`
453+ // RunnerToken is the secret of the runner, to present as "Authorization:
454+ // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is
455+ // returned once: the server keeps only its SHA-256, the runner must store
456+ // it (mode 0600). It stops working when the runner is removed.
457+ RunnerToken string `protobuf:"bytes,2,opt,name=runner_token,json=runnerToken,proto3" json:"runner_token,omitempty"`
458+ unknownFields protoimpl.UnknownFields
459+ sizeCache protoimpl.SizeCache
460+}
461+
462+func (x *RegisterRunnerResponse) Reset() {
463+ *x = RegisterRunnerResponse{}
464+ mi := &file_ermite_runner_proto_msgTypes[1]
465+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
466+ ms.StoreMessageInfo(mi)
467+}
468+
469+func (x *RegisterRunnerResponse) String() string {
470+ return protoimpl.X.MessageStringOf(x)
471+}
472+
473+func (*RegisterRunnerResponse) ProtoMessage() {}
474+
475+func (x *RegisterRunnerResponse) ProtoReflect() protoreflect.Message {
476+ mi := &file_ermite_runner_proto_msgTypes[1]
477+ if x != nil {
478+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
479+ if ms.LoadMessageInfo() == nil {
480+ ms.StoreMessageInfo(mi)
481+ }
482+ return ms
483+ }
484+ return mi.MessageOf(x)
485+}
486+
487+// Deprecated: Use RegisterRunnerResponse.ProtoReflect.Descriptor instead.
488+func (*RegisterRunnerResponse) Descriptor() ([]byte, []int) {
489+ return file_ermite_runner_proto_rawDescGZIP(), []int{1}
490+}
491+
492+func (x *RegisterRunnerResponse) GetRunnerId() string {
493+ if x != nil {
494+ return x.RunnerId
495+ }
496+ return ""
497+}
498+
499+func (x *RegisterRunnerResponse) GetRunnerToken() string {
500+ if x != nil {
501+ return x.RunnerToken
502+ }
503+ return ""
504+}
505+
506+// FetchJobRequest asks for a job.
507+type FetchJobRequest struct {
508+ state protoimpl.MessageState `protogen:"open.v1"`
509+ // Labels narrows the labels offered for this call. Empty means all the
510+ // labels of the runner, which is the normal case. Otherwise each one must be
511+ // a label of the runner (invalid_argument if not) and only jobs whose runs-on
512+ // labels are all among them are offered.
513+ Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"`
514+ unknownFields protoimpl.UnknownFields
515+ sizeCache protoimpl.SizeCache
516+}
517+
518+func (x *FetchJobRequest) Reset() {
519+ *x = FetchJobRequest{}
520+ mi := &file_ermite_runner_proto_msgTypes[2]
521+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
522+ ms.StoreMessageInfo(mi)
523+}
524+
525+func (x *FetchJobRequest) String() string {
526+ return protoimpl.X.MessageStringOf(x)
527+}
528+
529+func (*FetchJobRequest) ProtoMessage() {}
530+
531+func (x *FetchJobRequest) ProtoReflect() protoreflect.Message {
532+ mi := &file_ermite_runner_proto_msgTypes[2]
533+ if x != nil {
534+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
535+ if ms.LoadMessageInfo() == nil {
536+ ms.StoreMessageInfo(mi)
537+ }
538+ return ms
539+ }
540+ return mi.MessageOf(x)
541+}
542+
543+// Deprecated: Use FetchJobRequest.ProtoReflect.Descriptor instead.
544+func (*FetchJobRequest) Descriptor() ([]byte, []int) {
545+ return file_ermite_runner_proto_rawDescGZIP(), []int{2}
546+}
547+
548+func (x *FetchJobRequest) GetLabels() []string {
549+ if x != nil {
550+ return x.Labels
551+ }
552+ return nil
553+}
554+
555+// FetchJobResponse holds the job given to the runner, if any.
556+type FetchJobResponse struct {
557+ state protoimpl.MessageState `protogen:"open.v1"`
558+ // Job is the job to run. Unset when no job was available before the end of
559+ // the poll.
560+ Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"`
561+ unknownFields protoimpl.UnknownFields
562+ sizeCache protoimpl.SizeCache
563+}
564+
565+func (x *FetchJobResponse) Reset() {
566+ *x = FetchJobResponse{}
567+ mi := &file_ermite_runner_proto_msgTypes[3]
568+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
569+ ms.StoreMessageInfo(mi)
570+}
571+
572+func (x *FetchJobResponse) String() string {
573+ return protoimpl.X.MessageStringOf(x)
574+}
575+
576+func (*FetchJobResponse) ProtoMessage() {}
577+
578+func (x *FetchJobResponse) ProtoReflect() protoreflect.Message {
579+ mi := &file_ermite_runner_proto_msgTypes[3]
580+ if x != nil {
581+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
582+ if ms.LoadMessageInfo() == nil {
583+ ms.StoreMessageInfo(mi)
584+ }
585+ return ms
586+ }
587+ return mi.MessageOf(x)
588+}
589+
590+// Deprecated: Use FetchJobResponse.ProtoReflect.Descriptor instead.
591+func (*FetchJobResponse) Descriptor() ([]byte, []int) {
592+ return file_ermite_runner_proto_rawDescGZIP(), []int{3}
593+}
594+
595+func (x *FetchJobResponse) GetJob() *Job {
596+ if x != nil {
597+ return x.Job
598+ }
599+ return nil
600+}
601+
602+// Job is a job given to a runner: everything needed to run it, nothing more.
603+// The runner parses workflow_yaml itself; the protocol does not carry the
604+// workflow model, so it does not change when the model grows.
605+type Job struct {
606+ state protoimpl.MessageState `protogen:"open.v1"`
607+ // JobId identifies the job on the server. It is opaque, unique across
608+ // repositories and runs, and the same for every attempt of the job. With
609+ // attempt it names one execution.
610+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
611+ // RunId is the run the job belongs to, the "github.run_id". It is global to
612+ // the server.
613+ RunId int64 `protobuf:"varint,2,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"`
614+ // Attempt is the attempt of the run this job executes: 1 for the first, and
615+ // one more after each RunService.RerunRun. It is the "github.run_attempt".
616+ Attempt int32 `protobuf:"varint,3,opt,name=attempt,proto3" json:"attempt,omitempty"`
617+ // JobToken is the secret that authenticates this job's UpdateJob,
618+ // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer
619+ // <job_token>"). It is random, valid until the job ends, survives a server
620+ // restart and is shown only in this response. It must stay in the runner
621+ // host process and never reach the job's code.
622+ JobToken string `protobuf:"bytes,4,opt,name=job_token,json=jobToken,proto3" json:"job_token,omitempty"`
623+ // WorkflowPath is the path of the workflow file in the repository, for
624+ // example ".github/workflows/ci.yml".
625+ WorkflowPath string `protobuf:"bytes,5,opt,name=workflow_path,json=workflowPath,proto3" json:"workflow_path,omitempty"`
626+ // WorkflowYaml is the content of that file at sha, as is, at most 1 MiB
627+ // (larger files fail at planning).
628+ WorkflowYaml string `protobuf:"bytes,6,opt,name=workflow_yaml,json=workflowYaml,proto3" json:"workflow_yaml,omitempty"`
629+ // JobKey is the key of the job under "jobs:" in the workflow, the
630+ // "github.job".
631+ JobKey string `protobuf:"bytes,7,opt,name=job_key,json=jobKey,proto3" json:"job_key,omitempty"`
632+ // Matrix is the JSON object of the matrix values of this instance of the
633+ // job, for example {"os":"linux","go":"1.27"}. "{}" when the job has no
634+ // matrix. It is the "matrix" context.
635+ Matrix string `protobuf:"bytes,8,opt,name=matrix,proto3" json:"matrix,omitempty"`
636+ // Contexts is a JSON object with the contexts that the server computed for
637+ // this job: "github", "needs", "strategy", "matrix", "inputs" and "vars", in
638+ // the shape GitHub gives them. The contexts "job", "runner", "steps" and
639+ // "env" belong to the runner and are not in it. Other keys may be added
640+ // later; clients ignore those they do not know.
641+ Contexts string `protobuf:"bytes,9,opt,name=contexts,proto3" json:"contexts,omitempty"`
642+ // Labels are the runs-on labels of the job, evaluated.
643+ Labels []string `protobuf:"bytes,10,rep,name=labels,proto3" json:"labels,omitempty"`
644+ // TimeoutMinutes is the timeout of the job in minutes: the value of the
645+ // workflow's timeout-minutes, or 360 when it has none. At least 1. The
646+ // runner enforces it from the start of the job; the server ends a job that
647+ // exceeds it by announcing CANCEL_REASON_TIMEOUT.
648+ TimeoutMinutes int32 `protobuf:"varint,11,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"`
649+ // Repo is the name of the repository.
650+ Repo string `protobuf:"bytes,12,opt,name=repo,proto3" json:"repo,omitempty"`
651+ // Sha is the full id of the commit the run is for, 40 lowercase
652+ // hexadecimal characters for SHA-1. The runner reads the code at it.
653+ Sha string `protobuf:"bytes,13,opt,name=sha,proto3" json:"sha,omitempty"`
654+ // Ref is the full ref name that triggered the run, for example
655+ // "refs/heads/main" or "refs/tags/v1.0.0".
656+ Ref string `protobuf:"bytes,14,opt,name=ref,proto3" json:"ref,omitempty"`
657+ unknownFields protoimpl.UnknownFields
658+ sizeCache protoimpl.SizeCache
659+}
660+
661+func (x *Job) Reset() {
662+ *x = Job{}
663+ mi := &file_ermite_runner_proto_msgTypes[4]
664+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
665+ ms.StoreMessageInfo(mi)
666+}
667+
668+func (x *Job) String() string {
669+ return protoimpl.X.MessageStringOf(x)
670+}
671+
672+func (*Job) ProtoMessage() {}
673+
674+func (x *Job) ProtoReflect() protoreflect.Message {
675+ mi := &file_ermite_runner_proto_msgTypes[4]
676+ if x != nil {
677+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
678+ if ms.LoadMessageInfo() == nil {
679+ ms.StoreMessageInfo(mi)
680+ }
681+ return ms
682+ }
683+ return mi.MessageOf(x)
684+}
685+
686+// Deprecated: Use Job.ProtoReflect.Descriptor instead.
687+func (*Job) Descriptor() ([]byte, []int) {
688+ return file_ermite_runner_proto_rawDescGZIP(), []int{4}
689+}
690+
691+func (x *Job) GetJobId() string {
692+ if x != nil {
693+ return x.JobId
694+ }
695+ return ""
696+}
697+
698+func (x *Job) GetRunId() int64 {
699+ if x != nil {
700+ return x.RunId
701+ }
702+ return 0
703+}
704+
705+func (x *Job) GetAttempt() int32 {
706+ if x != nil {
707+ return x.Attempt
708+ }
709+ return 0
710+}
711+
712+func (x *Job) GetJobToken() string {
713+ if x != nil {
714+ return x.JobToken
715+ }
716+ return ""
717+}
718+
719+func (x *Job) GetWorkflowPath() string {
720+ if x != nil {
721+ return x.WorkflowPath
722+ }
723+ return ""
724+}
725+
726+func (x *Job) GetWorkflowYaml() string {
727+ if x != nil {
728+ return x.WorkflowYaml
729+ }
730+ return ""
731+}
732+
733+func (x *Job) GetJobKey() string {
734+ if x != nil {
735+ return x.JobKey
736+ }
737+ return ""
738+}
739+
740+func (x *Job) GetMatrix() string {
741+ if x != nil {
742+ return x.Matrix
743+ }
744+ return ""
745+}
746+
747+func (x *Job) GetContexts() string {
748+ if x != nil {
749+ return x.Contexts
750+ }
751+ return ""
752+}
753+
754+func (x *Job) GetLabels() []string {
755+ if x != nil {
756+ return x.Labels
757+ }
758+ return nil
759+}
760+
761+func (x *Job) GetTimeoutMinutes() int32 {
762+ if x != nil {
763+ return x.TimeoutMinutes
764+ }
765+ return 0
766+}
767+
768+func (x *Job) GetRepo() string {
769+ if x != nil {
770+ return x.Repo
771+ }
772+ return ""
773+}
774+
775+func (x *Job) GetSha() string {
776+ if x != nil {
777+ return x.Sha
778+ }
779+ return ""
780+}
781+
782+func (x *Job) GetRef() string {
783+ if x != nil {
784+ return x.Ref
785+ }
786+ return ""
787+}
788+
789+// UpdateJobRequest reports the steps and, in the last call, the end of a job.
790+type UpdateJobRequest struct {
791+ state protoimpl.MessageState `protogen:"open.v1"`
792+ // JobId is the Job.job_id. It must be the job of the token.
793+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
794+ // Steps are the steps whose state changed or that are new. Each one is a
795+ // full snapshot of its step, applied by number. At most 1,000 steps per job
796+ // (invalid_argument beyond). Steps are numbered like GitHub's log: the
797+ // implicit "Set up job" is 1, the steps of the workflow follow in order, and
798+ // the implicit "Complete job" is last.
799+ Steps []*StepUpdate `protobuf:"bytes,2,rep,name=steps,proto3" json:"steps,omitempty"`
800+ // Outputs are the outputs of the job, evaluated by the runner. Only allowed
801+ // in the call that carries the conclusion (invalid_argument otherwise).
802+ // Names are 1 to 255 characters among letters, digits, "_" and "-" and
803+ // begin with a letter or "_" and are case-insensitive: two names that
804+ // differ only by case are invalid_argument. Each value is at most 1 MiB, and
805+ // all names and values together at most 1 MiB (1,048,576 bytes); beyond that
806+ // the whole call is rejected with invalid_argument and the runner reports
807+ // the failure of the job without outputs, with a line in the log.
808+ Outputs map[string]string `protobuf:"bytes,3,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
809+ // Conclusion ends the job when set. A runner reports CONCLUSION_SUCCESS,
810+ // CONCLUSION_FAILURE, CONCLUSION_CANCELLED or CONCLUSION_TIMED_OUT; any
811+ // other value is invalid_argument. CONCLUSION_UNSPECIFIED means the job goes
812+ // on. Steps that were not reported COMPLETED when the job ends are
813+ // completed by the server as CONCLUSION_CANCELLED (or SKIPPED when they
814+ // never started and the job did not fail by cancellation).
815+ Conclusion Conclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.Conclusion" json:"conclusion,omitempty"`
816+ // RunnerShutdown is true when the runner ends the job because it was told to
817+ // stop (service stopped, signal). Only allowed with CONCLUSION_CANCELLED.
818+ // The server then annotates the job with GitHub's message "The runner has
819+ // received a shutdown signal. This can happen when the runner service is
820+ // stopped, or a manually started runner is canceled."
821+ RunnerShutdown bool `protobuf:"varint,5,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"`
822+ unknownFields protoimpl.UnknownFields
823+ sizeCache protoimpl.SizeCache
824+}
825+
826+func (x *UpdateJobRequest) Reset() {
827+ *x = UpdateJobRequest{}
828+ mi := &file_ermite_runner_proto_msgTypes[5]
829+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
830+ ms.StoreMessageInfo(mi)
831+}
832+
833+func (x *UpdateJobRequest) String() string {
834+ return protoimpl.X.MessageStringOf(x)
835+}
836+
837+func (*UpdateJobRequest) ProtoMessage() {}
838+
839+func (x *UpdateJobRequest) ProtoReflect() protoreflect.Message {
840+ mi := &file_ermite_runner_proto_msgTypes[5]
841+ if x != nil {
842+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
843+ if ms.LoadMessageInfo() == nil {
844+ ms.StoreMessageInfo(mi)
845+ }
846+ return ms
847+ }
848+ return mi.MessageOf(x)
849+}
850+
851+// Deprecated: Use UpdateJobRequest.ProtoReflect.Descriptor instead.
852+func (*UpdateJobRequest) Descriptor() ([]byte, []int) {
853+ return file_ermite_runner_proto_rawDescGZIP(), []int{5}
854+}
855+
856+func (x *UpdateJobRequest) GetJobId() string {
857+ if x != nil {
858+ return x.JobId
859+ }
860+ return ""
861+}
862+
863+func (x *UpdateJobRequest) GetSteps() []*StepUpdate {
864+ if x != nil {
865+ return x.Steps
866+ }
867+ return nil
868+}
869+
870+func (x *UpdateJobRequest) GetOutputs() map[string]string {
871+ if x != nil {
872+ return x.Outputs
873+ }
874+ return nil
875+}
876+
877+func (x *UpdateJobRequest) GetConclusion() Conclusion {
878+ if x != nil {
879+ return x.Conclusion
880+ }
881+ return Conclusion_CONCLUSION_UNSPECIFIED
882+}
883+
884+func (x *UpdateJobRequest) GetRunnerShutdown() bool {
885+ if x != nil {
886+ return x.RunnerShutdown
887+ }
888+ return false
889+}
890+
891+// StepUpdate is the state of one step as the runner knows it.
892+type StepUpdate struct {
893+ state protoimpl.MessageState `protogen:"open.v1"`
894+ // Number is the 1-based number of the step in the job. It is the number
895+ // used by AppendLog.
896+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
897+ // Name is the display name of the step, evaluated: the "name" of the
898+ // workflow step, or "Run <first line of the script>", as GitHub shows it.
899+ // At most 1,024 characters; the server cuts longer names.
900+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
901+ // State is where the step is.
902+ State StepState `protobuf:"varint,3,opt,name=state,proto3,enum=ermite.StepState" json:"state,omitempty"`
903+ // Conclusion is how the step ended. Required (not UNSPECIFIED) when state is
904+ // COMPLETED, must be UNSPECIFIED otherwise. Allowed values: success,
905+ // failure, cancelled, skipped.
906+ Conclusion Conclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.Conclusion" json:"conclusion,omitempty"`
907+ // StartedAt is when the step started, by the clock of the runner. Required
908+ // from IN_PROGRESS on.
909+ StartedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"`
910+ // CompletedAt is when the step ended, by the clock of the runner. Required
911+ // when state is COMPLETED.
912+ CompletedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"`
913+ unknownFields protoimpl.UnknownFields
914+ sizeCache protoimpl.SizeCache
915+}
916+
917+func (x *StepUpdate) Reset() {
918+ *x = StepUpdate{}
919+ mi := &file_ermite_runner_proto_msgTypes[6]
920+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
921+ ms.StoreMessageInfo(mi)
922+}
923+
924+func (x *StepUpdate) String() string {
925+ return protoimpl.X.MessageStringOf(x)
926+}
927+
928+func (*StepUpdate) ProtoMessage() {}
929+
930+func (x *StepUpdate) ProtoReflect() protoreflect.Message {
931+ mi := &file_ermite_runner_proto_msgTypes[6]
932+ if x != nil {
933+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
934+ if ms.LoadMessageInfo() == nil {
935+ ms.StoreMessageInfo(mi)
936+ }
937+ return ms
938+ }
939+ return mi.MessageOf(x)
940+}
941+
942+// Deprecated: Use StepUpdate.ProtoReflect.Descriptor instead.
943+func (*StepUpdate) Descriptor() ([]byte, []int) {
944+ return file_ermite_runner_proto_rawDescGZIP(), []int{6}
945+}
946+
947+func (x *StepUpdate) GetNumber() int32 {
948+ if x != nil {
949+ return x.Number
950+ }
951+ return 0
952+}
953+
954+func (x *StepUpdate) GetName() string {
955+ if x != nil {
956+ return x.Name
957+ }
958+ return ""
959+}
960+
961+func (x *StepUpdate) GetState() StepState {
962+ if x != nil {
963+ return x.State
964+ }
965+ return StepState_STEP_STATE_UNSPECIFIED
966+}
967+
968+func (x *StepUpdate) GetConclusion() Conclusion {
969+ if x != nil {
970+ return x.Conclusion
971+ }
972+ return Conclusion_CONCLUSION_UNSPECIFIED
973+}
974+
975+func (x *StepUpdate) GetStartedAt() *timestamppb.Timestamp {
976+ if x != nil {
977+ return x.StartedAt
978+ }
979+ return nil
980+}
981+
982+func (x *StepUpdate) GetCompletedAt() *timestamppb.Timestamp {
983+ if x != nil {
984+ return x.CompletedAt
985+ }
986+ return nil
987+}
988+
989+// UpdateJobResponse is empty.
990+type UpdateJobResponse struct {
991+ state protoimpl.MessageState `protogen:"open.v1"`
992+ unknownFields protoimpl.UnknownFields
993+ sizeCache protoimpl.SizeCache
994+}
995+
996+func (x *UpdateJobResponse) Reset() {
997+ *x = UpdateJobResponse{}
998+ mi := &file_ermite_runner_proto_msgTypes[7]
999+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1000+ ms.StoreMessageInfo(mi)
1001+}
1002+
1003+func (x *UpdateJobResponse) String() string {
1004+ return protoimpl.X.MessageStringOf(x)
1005+}
1006+
1007+func (*UpdateJobResponse) ProtoMessage() {}
1008+
1009+func (x *UpdateJobResponse) ProtoReflect() protoreflect.Message {
1010+ mi := &file_ermite_runner_proto_msgTypes[7]
1011+ if x != nil {
1012+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1013+ if ms.LoadMessageInfo() == nil {
1014+ ms.StoreMessageInfo(mi)
1015+ }
1016+ return ms
1017+ }
1018+ return mi.MessageOf(x)
1019+}
1020+
1021+// Deprecated: Use UpdateJobResponse.ProtoReflect.Descriptor instead.
1022+func (*UpdateJobResponse) Descriptor() ([]byte, []int) {
1023+ return file_ermite_runner_proto_rawDescGZIP(), []int{7}
1024+}
1025+
1026+// AppendLogRequest carries log lines of one step.
1027+type AppendLogRequest struct {
1028+ state protoimpl.MessageState `protogen:"open.v1"`
1029+ // JobId is the Job.job_id. It must be the job of the token.
1030+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1031+ // Step is the number of the step the lines belong to (see
1032+ // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not
1033+ // announced yet is created by the first lines.
1034+ Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"`
1035+ // Offset is the number of the first line of the request in the log of the
1036+ // job, counted from 0 across all steps. See the rules on AppendLog.
1037+ Offset int64 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"`
1038+ // Lines are the lines to append, in order: at most 1,000, at most 64 KiB of
1039+ // text in total.
1040+ Lines []*LogLine `protobuf:"bytes,4,rep,name=lines,proto3" json:"lines,omitempty"`
1041+ unknownFields protoimpl.UnknownFields
1042+ sizeCache protoimpl.SizeCache
1043+}
1044+
1045+func (x *AppendLogRequest) Reset() {
1046+ *x = AppendLogRequest{}
1047+ mi := &file_ermite_runner_proto_msgTypes[8]
1048+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1049+ ms.StoreMessageInfo(mi)
1050+}
1051+
1052+func (x *AppendLogRequest) String() string {
1053+ return protoimpl.X.MessageStringOf(x)
1054+}
1055+
1056+func (*AppendLogRequest) ProtoMessage() {}
1057+
1058+func (x *AppendLogRequest) ProtoReflect() protoreflect.Message {
1059+ mi := &file_ermite_runner_proto_msgTypes[8]
1060+ if x != nil {
1061+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1062+ if ms.LoadMessageInfo() == nil {
1063+ ms.StoreMessageInfo(mi)
1064+ }
1065+ return ms
1066+ }
1067+ return mi.MessageOf(x)
1068+}
1069+
1070+// Deprecated: Use AppendLogRequest.ProtoReflect.Descriptor instead.
1071+func (*AppendLogRequest) Descriptor() ([]byte, []int) {
1072+ return file_ermite_runner_proto_rawDescGZIP(), []int{8}
1073+}
1074+
1075+func (x *AppendLogRequest) GetJobId() string {
1076+ if x != nil {
1077+ return x.JobId
1078+ }
1079+ return ""
1080+}
1081+
1082+func (x *AppendLogRequest) GetStep() int32 {
1083+ if x != nil {
1084+ return x.Step
1085+ }
1086+ return 0
1087+}
1088+
1089+func (x *AppendLogRequest) GetOffset() int64 {
1090+ if x != nil {
1091+ return x.Offset
1092+ }
1093+ return 0
1094+}
1095+
1096+func (x *AppendLogRequest) GetLines() []*LogLine {
1097+ if x != nil {
1098+ return x.Lines
1099+ }
1100+ return nil
1101+}
1102+
1103+// LogLine is one line of a job log.
1104+type LogLine struct {
1105+ state protoimpl.MessageState `protogen:"open.v1"`
1106+ // Time is when the line was produced, by the clock of the runner. The server
1107+ // keeps it with nanosecond precision (RFC 3339) and does not reorder lines.
1108+ Time *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=time,proto3" json:"time,omitempty"`
1109+ // Text is the line without its line terminator, valid UTF-8 (the client
1110+ // replaces invalid bytes with U+FFFD). It must not contain a line feed; a
1111+ // carriage return is kept. It is the line as GitHub's raw log has it,
1112+ // including "##[group]" and "##[endgroup]" markers and "##[error]" lines.
1113+ Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"`
1114+ unknownFields protoimpl.UnknownFields
1115+ sizeCache protoimpl.SizeCache
1116+}
1117+
1118+func (x *LogLine) Reset() {
1119+ *x = LogLine{}
1120+ mi := &file_ermite_runner_proto_msgTypes[9]
1121+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1122+ ms.StoreMessageInfo(mi)
1123+}
1124+
1125+func (x *LogLine) String() string {
1126+ return protoimpl.X.MessageStringOf(x)
1127+}
1128+
1129+func (*LogLine) ProtoMessage() {}
1130+
1131+func (x *LogLine) ProtoReflect() protoreflect.Message {
1132+ mi := &file_ermite_runner_proto_msgTypes[9]
1133+ if x != nil {
1134+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1135+ if ms.LoadMessageInfo() == nil {
1136+ ms.StoreMessageInfo(mi)
1137+ }
1138+ return ms
1139+ }
1140+ return mi.MessageOf(x)
1141+}
1142+
1143+// Deprecated: Use LogLine.ProtoReflect.Descriptor instead.
1144+func (*LogLine) Descriptor() ([]byte, []int) {
1145+ return file_ermite_runner_proto_rawDescGZIP(), []int{9}
1146+}
1147+
1148+func (x *LogLine) GetTime() *timestamppb.Timestamp {
1149+ if x != nil {
1150+ return x.Time
1151+ }
1152+ return nil
1153+}
1154+
1155+func (x *LogLine) GetText() string {
1156+ if x != nil {
1157+ return x.Text
1158+ }
1159+ return ""
1160+}
1161+
1162+// AppendLogResponse tells how much of the log the server holds.
1163+type AppendLogResponse struct {
1164+ state protoimpl.MessageState `protogen:"open.v1"`
1165+ // NextOffset is the number of lines of the job log after the call, which is
1166+ // the offset of the next request.
1167+ NextOffset int64 `protobuf:"varint,1,opt,name=next_offset,json=nextOffset,proto3" json:"next_offset,omitempty"`
1168+ unknownFields protoimpl.UnknownFields
1169+ sizeCache protoimpl.SizeCache
1170+}
1171+
1172+func (x *AppendLogResponse) Reset() {
1173+ *x = AppendLogResponse{}
1174+ mi := &file_ermite_runner_proto_msgTypes[10]
1175+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1176+ ms.StoreMessageInfo(mi)
1177+}
1178+
1179+func (x *AppendLogResponse) String() string {
1180+ return protoimpl.X.MessageStringOf(x)
1181+}
1182+
1183+func (*AppendLogResponse) ProtoMessage() {}
1184+
1185+func (x *AppendLogResponse) ProtoReflect() protoreflect.Message {
1186+ mi := &file_ermite_runner_proto_msgTypes[10]
1187+ if x != nil {
1188+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1189+ if ms.LoadMessageInfo() == nil {
1190+ ms.StoreMessageInfo(mi)
1191+ }
1192+ return ms
1193+ }
1194+ return mi.MessageOf(x)
1195+}
1196+
1197+// Deprecated: Use AppendLogResponse.ProtoReflect.Descriptor instead.
1198+func (*AppendLogResponse) Descriptor() ([]byte, []int) {
1199+ return file_ermite_runner_proto_rawDescGZIP(), []int{10}
1200+}
1201+
1202+func (x *AppendLogResponse) GetNextOffset() int64 {
1203+ if x != nil {
1204+ return x.NextOffset
1205+ }
1206+ return 0
1207+}
1208+
1209+// HeartbeatRequest names the job that is alive.
1210+type HeartbeatRequest struct {
1211+ state protoimpl.MessageState `protogen:"open.v1"`
1212+ // JobId is the Job.job_id. It must be the job of the token.
1213+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1214+ unknownFields protoimpl.UnknownFields
1215+ sizeCache protoimpl.SizeCache
1216+}
1217+
1218+func (x *HeartbeatRequest) Reset() {
1219+ *x = HeartbeatRequest{}
1220+ mi := &file_ermite_runner_proto_msgTypes[11]
1221+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1222+ ms.StoreMessageInfo(mi)
1223+}
1224+
1225+func (x *HeartbeatRequest) String() string {
1226+ return protoimpl.X.MessageStringOf(x)
1227+}
1228+
1229+func (*HeartbeatRequest) ProtoMessage() {}
1230+
1231+func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message {
1232+ mi := &file_ermite_runner_proto_msgTypes[11]
1233+ if x != nil {
1234+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1235+ if ms.LoadMessageInfo() == nil {
1236+ ms.StoreMessageInfo(mi)
1237+ }
1238+ return ms
1239+ }
1240+ return mi.MessageOf(x)
1241+}
1242+
1243+// Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead.
1244+func (*HeartbeatRequest) Descriptor() ([]byte, []int) {
1245+ return file_ermite_runner_proto_rawDescGZIP(), []int{11}
1246+}
1247+
1248+func (x *HeartbeatRequest) GetJobId() string {
1249+ if x != nil {
1250+ return x.JobId
1251+ }
1252+ return ""
1253+}
1254+
1255+// HeartbeatResponse is empty.
1256+type HeartbeatResponse struct {
1257+ state protoimpl.MessageState `protogen:"open.v1"`
1258+ unknownFields protoimpl.UnknownFields
1259+ sizeCache protoimpl.SizeCache
1260+}
1261+
1262+func (x *HeartbeatResponse) Reset() {
1263+ *x = HeartbeatResponse{}
1264+ mi := &file_ermite_runner_proto_msgTypes[12]
1265+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1266+ ms.StoreMessageInfo(mi)
1267+}
1268+
1269+func (x *HeartbeatResponse) String() string {
1270+ return protoimpl.X.MessageStringOf(x)
1271+}
1272+
1273+func (*HeartbeatResponse) ProtoMessage() {}
1274+
1275+func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message {
1276+ mi := &file_ermite_runner_proto_msgTypes[12]
1277+ if x != nil {
1278+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1279+ if ms.LoadMessageInfo() == nil {
1280+ ms.StoreMessageInfo(mi)
1281+ }
1282+ return ms
1283+ }
1284+ return mi.MessageOf(x)
1285+}
1286+
1287+// Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead.
1288+func (*HeartbeatResponse) Descriptor() ([]byte, []int) {
1289+ return file_ermite_runner_proto_rawDescGZIP(), []int{12}
1290+}
1291+
1292+// WatchJobRequest names the job to watch.
1293+type WatchJobRequest struct {
1294+ state protoimpl.MessageState `protogen:"open.v1"`
1295+ // JobId is the Job.job_id. It must be the job of the token.
1296+ JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"`
1297+ unknownFields protoimpl.UnknownFields
1298+ sizeCache protoimpl.SizeCache
1299+}
1300+
1301+func (x *WatchJobRequest) Reset() {
1302+ *x = WatchJobRequest{}
1303+ mi := &file_ermite_runner_proto_msgTypes[13]
1304+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1305+ ms.StoreMessageInfo(mi)
1306+}
1307+
1308+func (x *WatchJobRequest) String() string {
1309+ return protoimpl.X.MessageStringOf(x)
1310+}
1311+
1312+func (*WatchJobRequest) ProtoMessage() {}
1313+
1314+func (x *WatchJobRequest) ProtoReflect() protoreflect.Message {
1315+ mi := &file_ermite_runner_proto_msgTypes[13]
1316+ if x != nil {
1317+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1318+ if ms.LoadMessageInfo() == nil {
1319+ ms.StoreMessageInfo(mi)
1320+ }
1321+ return ms
1322+ }
1323+ return mi.MessageOf(x)
1324+}
1325+
1326+// Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead.
1327+func (*WatchJobRequest) Descriptor() ([]byte, []int) {
1328+ return file_ermite_runner_proto_rawDescGZIP(), []int{13}
1329+}
1330+
1331+func (x *WatchJobRequest) GetJobId() string {
1332+ if x != nil {
1333+ return x.JobId
1334+ }
1335+ return ""
1336+}
1337+
1338+// WatchJobResponse is one message of the stream: a request to cancel the job,
1339+// or a keep-alive.
1340+type WatchJobResponse struct {
1341+ state protoimpl.MessageState `protogen:"open.v1"`
1342+ // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a
1343+ // keep-alive: the other fields are empty and the client ignores it. New
1344+ // reasons may be added; a runner that does not know one cancels the job and
1345+ // reports CONCLUSION_CANCELLED.
1346+ Reason CancelReason `protobuf:"varint,1,opt,name=reason,proto3,enum=ermite.CancelReason" json:"reason,omitempty"`
1347+ // Message is a short human-readable explanation, for the job log, for
1348+ // example "Canceled by the owner". At most 1,024 characters.
1349+ Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
1350+ // RequestedAt is when the cancellation was requested, by the clock of the
1351+ // server.
1352+ RequestedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=requested_at,json=requestedAt,proto3" json:"requested_at,omitempty"`
1353+ unknownFields protoimpl.UnknownFields
1354+ sizeCache protoimpl.SizeCache
1355+}
1356+
1357+func (x *WatchJobResponse) Reset() {
1358+ *x = WatchJobResponse{}
1359+ mi := &file_ermite_runner_proto_msgTypes[14]
1360+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1361+ ms.StoreMessageInfo(mi)
1362+}
1363+
1364+func (x *WatchJobResponse) String() string {
1365+ return protoimpl.X.MessageStringOf(x)
1366+}
1367+
1368+func (*WatchJobResponse) ProtoMessage() {}
1369+
1370+func (x *WatchJobResponse) ProtoReflect() protoreflect.Message {
1371+ mi := &file_ermite_runner_proto_msgTypes[14]
1372+ if x != nil {
1373+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1374+ if ms.LoadMessageInfo() == nil {
1375+ ms.StoreMessageInfo(mi)
1376+ }
1377+ return ms
1378+ }
1379+ return mi.MessageOf(x)
1380+}
1381+
1382+// Deprecated: Use WatchJobResponse.ProtoReflect.Descriptor instead.
1383+func (*WatchJobResponse) Descriptor() ([]byte, []int) {
1384+ return file_ermite_runner_proto_rawDescGZIP(), []int{14}
1385+}
1386+
1387+func (x *WatchJobResponse) GetReason() CancelReason {
1388+ if x != nil {
1389+ return x.Reason
1390+ }
1391+ return CancelReason_CANCEL_REASON_UNSPECIFIED
1392+}
1393+
1394+func (x *WatchJobResponse) GetMessage() string {
1395+ if x != nil {
1396+ return x.Message
1397+ }
1398+ return ""
1399+}
1400+
1401+func (x *WatchJobResponse) GetRequestedAt() *timestamppb.Timestamp {
1402+ if x != nil {
1403+ return x.RequestedAt
1404+ }
1405+ return nil
1406+}
1407+
1408+var File_ermite_runner_proto protoreflect.FileDescriptor
1409+
1410+const file_ermite_runner_proto_rawDesc = "" +
1411+ "\n" +
1412+ "\x13ermite/runner.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb8\x01\n" +
1413+ "\x15RegisterRunnerRequest\x12\x12\n" +
1414+ "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
1415+ "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x18\n" +
1416+ "\aversion\x18\x03 \x01(\tR\aversion\x125\n" +
1417+ "\tisolation\x18\x04 \x01(\x0e2\x17.ermite.RunnerIsolationR\tisolation\x12\x12\n" +
1418+ "\x04arch\x18\x05 \x01(\tR\x04arch\x12\x0e\n" +
1419+ "\x02os\x18\x06 \x01(\tR\x02os\"X\n" +
1420+ "\x16RegisterRunnerResponse\x12\x1b\n" +
1421+ "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" +
1422+ "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\")\n" +
1423+ "\x0fFetchJobRequest\x12\x16\n" +
1424+ "\x06labels\x18\x01 \x03(\tR\x06labels\"1\n" +
1425+ "\x10FetchJobResponse\x12\x1d\n" +
1426+ "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\"\xfa\x02\n" +
1427+ "\x03Job\x12\x15\n" +
1428+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" +
1429+ "\x06run_id\x18\x02 \x01(\x03R\x05runId\x12\x18\n" +
1430+ "\aattempt\x18\x03 \x01(\x05R\aattempt\x12\x1b\n" +
1431+ "\tjob_token\x18\x04 \x01(\tR\bjobToken\x12#\n" +
1432+ "\rworkflow_path\x18\x05 \x01(\tR\fworkflowPath\x12#\n" +
1433+ "\rworkflow_yaml\x18\x06 \x01(\tR\fworkflowYaml\x12\x17\n" +
1434+ "\ajob_key\x18\a \x01(\tR\x06jobKey\x12\x16\n" +
1435+ "\x06matrix\x18\b \x01(\tR\x06matrix\x12\x1a\n" +
1436+ "\bcontexts\x18\t \x01(\tR\bcontexts\x12\x16\n" +
1437+ "\x06labels\x18\n" +
1438+ " \x03(\tR\x06labels\x12'\n" +
1439+ "\x0ftimeout_minutes\x18\v \x01(\x05R\x0etimeoutMinutes\x12\x12\n" +
1440+ "\x04repo\x18\f \x01(\tR\x04repo\x12\x10\n" +
1441+ "\x03sha\x18\r \x01(\tR\x03sha\x12\x10\n" +
1442+ "\x03ref\x18\x0e \x01(\tR\x03ref\"\xad\x02\n" +
1443+ "\x10UpdateJobRequest\x12\x15\n" +
1444+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12(\n" +
1445+ "\x05steps\x18\x02 \x03(\v2\x12.ermite.StepUpdateR\x05steps\x12?\n" +
1446+ "\aoutputs\x18\x03 \x03(\v2%.ermite.UpdateJobRequest.OutputsEntryR\aoutputs\x122\n" +
1447+ "\n" +
1448+ "conclusion\x18\x04 \x01(\x0e2\x12.ermite.ConclusionR\n" +
1449+ "conclusion\x12'\n" +
1450+ "\x0frunner_shutdown\x18\x05 \x01(\bR\x0erunnerShutdown\x1a:\n" +
1451+ "\fOutputsEntry\x12\x10\n" +
1452+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1453+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8f\x02\n" +
1454+ "\n" +
1455+ "StepUpdate\x12\x16\n" +
1456+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1457+ "\x04name\x18\x02 \x01(\tR\x04name\x12'\n" +
1458+ "\x05state\x18\x03 \x01(\x0e2\x11.ermite.StepStateR\x05state\x122\n" +
1459+ "\n" +
1460+ "conclusion\x18\x04 \x01(\x0e2\x12.ermite.ConclusionR\n" +
1461+ "conclusion\x129\n" +
1462+ "\n" +
1463+ "started_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" +
1464+ "\fcompleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\"\x13\n" +
1465+ "\x11UpdateJobResponse\"|\n" +
1466+ "\x10AppendLogRequest\x12\x15\n" +
1467+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x12\n" +
1468+ "\x04step\x18\x02 \x01(\x05R\x04step\x12\x16\n" +
1469+ "\x06offset\x18\x03 \x01(\x03R\x06offset\x12%\n" +
1470+ "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"M\n" +
1471+ "\aLogLine\x12.\n" +
1472+ "\x04time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x12\x12\n" +
1473+ "\x04text\x18\x02 \x01(\tR\x04text\"4\n" +
1474+ "\x11AppendLogResponse\x12\x1f\n" +
1475+ "\vnext_offset\x18\x01 \x01(\x03R\n" +
1476+ "nextOffset\")\n" +
1477+ "\x10HeartbeatRequest\x12\x15\n" +
1478+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x13\n" +
1479+ "\x11HeartbeatResponse\"(\n" +
1480+ "\x0fWatchJobRequest\x12\x15\n" +
1481+ "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x99\x01\n" +
1482+ "\x10WatchJobResponse\x12,\n" +
1483+ "\x06reason\x18\x01 \x01(\x0e2\x14.ermite.CancelReasonR\x06reason\x12\x18\n" +
1484+ "\amessage\x18\x02 \x01(\tR\amessage\x12=\n" +
1485+ "\frequested_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt*t\n" +
1486+ "\tRunStatus\x12\x1a\n" +
1487+ "\x16RUN_STATUS_UNSPECIFIED\x10\x00\x12\x15\n" +
1488+ "\x11RUN_STATUS_QUEUED\x10\x01\x12\x1a\n" +
1489+ "\x16RUN_STATUS_IN_PROGRESS\x10\x02\x12\x18\n" +
1490+ "\x14RUN_STATUS_COMPLETED\x10\x03*\xdc\x01\n" +
1491+ "\n" +
1492+ "Conclusion\x12\x1a\n" +
1493+ "\x16CONCLUSION_UNSPECIFIED\x10\x00\x12\x16\n" +
1494+ "\x12CONCLUSION_SUCCESS\x10\x01\x12\x16\n" +
1495+ "\x12CONCLUSION_FAILURE\x10\x02\x12\x18\n" +
1496+ "\x14CONCLUSION_CANCELLED\x10\x03\x12\x16\n" +
1497+ "\x12CONCLUSION_SKIPPED\x10\x04\x12\x18\n" +
1498+ "\x14CONCLUSION_TIMED_OUT\x10\x05\x12\x16\n" +
1499+ "\x12CONCLUSION_NEUTRAL\x10\x06\x12\x1e\n" +
1500+ "\x1aCONCLUSION_ACTION_REQUIRED\x10\a*u\n" +
1501+ "\tStepState\x12\x1a\n" +
1502+ "\x16STEP_STATE_UNSPECIFIED\x10\x00\x12\x16\n" +
1503+ "\x12STEP_STATE_PENDING\x10\x01\x12\x1a\n" +
1504+ "\x16STEP_STATE_IN_PROGRESS\x10\x02\x12\x18\n" +
1505+ "\x14STEP_STATE_COMPLETED\x10\x03*\x9e\x01\n" +
1506+ "\x0fRunnerIsolation\x12 \n" +
1507+ "\x1cRUNNER_ISOLATION_UNSPECIFIED\x10\x00\x12\x17\n" +
1508+ "\x13RUNNER_ISOLATION_VM\x10\x01\x12%\n" +
1509+ "!RUNNER_ISOLATION_CONTAINER_SYSBOX\x10\x02\x12)\n" +
1510+ "%RUNNER_ISOLATION_CONTAINER_PRIVILEGED\x10\x03*}\n" +
1511+ "\fCancelReason\x12\x1d\n" +
1512+ "\x19CANCEL_REASON_UNSPECIFIED\x10\x00\x12\x16\n" +
1513+ "\x12CANCEL_REASON_USER\x10\x01\x12\x1b\n" +
1514+ "\x17CANCEL_REASON_FAIL_FAST\x10\x02\x12\x19\n" +
1515+ "\x15CANCEL_REASON_TIMEOUT\x10\x032\xa6\x03\n" +
1516+ "\rRunnerService\x12O\n" +
1517+ "\x0eRegisterRunner\x12\x1d.ermite.RegisterRunnerRequest\x1a\x1e.ermite.RegisterRunnerResponse\x12=\n" +
1518+ "\bFetchJob\x12\x17.ermite.FetchJobRequest\x1a\x18.ermite.FetchJobResponse\x12@\n" +
1519+ "\tUpdateJob\x12\x18.ermite.UpdateJobRequest\x1a\x19.ermite.UpdateJobResponse\x12@\n" +
1520+ "\tAppendLog\x12\x18.ermite.AppendLogRequest\x1a\x19.ermite.AppendLogResponse\x12@\n" +
1521+ "\tHeartbeat\x12\x18.ermite.HeartbeatRequest\x1a\x19.ermite.HeartbeatResponse\x12?\n" +
1522+ "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01Br\n" +
1523+ "\n" +
1524+ "com.ermiteB\vRunnerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
1525+
1526+var (
1527+ file_ermite_runner_proto_rawDescOnce sync.Once
1528+ file_ermite_runner_proto_rawDescData []byte
1529+)
1530+
1531+func file_ermite_runner_proto_rawDescGZIP() []byte {
1532+ file_ermite_runner_proto_rawDescOnce.Do(func() {
1533+ file_ermite_runner_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)))
1534+ })
1535+ return file_ermite_runner_proto_rawDescData
1536+}
1537+
1538+var file_ermite_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
1539+var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
1540+var file_ermite_runner_proto_goTypes = []any{
1541+ (RunStatus)(0), // 0: ermite.RunStatus
1542+ (Conclusion)(0), // 1: ermite.Conclusion
1543+ (StepState)(0), // 2: ermite.StepState
1544+ (RunnerIsolation)(0), // 3: ermite.RunnerIsolation
1545+ (CancelReason)(0), // 4: ermite.CancelReason
1546+ (*RegisterRunnerRequest)(nil), // 5: ermite.RegisterRunnerRequest
1547+ (*RegisterRunnerResponse)(nil), // 6: ermite.RegisterRunnerResponse
1548+ (*FetchJobRequest)(nil), // 7: ermite.FetchJobRequest
1549+ (*FetchJobResponse)(nil), // 8: ermite.FetchJobResponse
1550+ (*Job)(nil), // 9: ermite.Job
1551+ (*UpdateJobRequest)(nil), // 10: ermite.UpdateJobRequest
1552+ (*StepUpdate)(nil), // 11: ermite.StepUpdate
1553+ (*UpdateJobResponse)(nil), // 12: ermite.UpdateJobResponse
1554+ (*AppendLogRequest)(nil), // 13: ermite.AppendLogRequest
1555+ (*LogLine)(nil), // 14: ermite.LogLine
1556+ (*AppendLogResponse)(nil), // 15: ermite.AppendLogResponse
1557+ (*HeartbeatRequest)(nil), // 16: ermite.HeartbeatRequest
1558+ (*HeartbeatResponse)(nil), // 17: ermite.HeartbeatResponse
1559+ (*WatchJobRequest)(nil), // 18: ermite.WatchJobRequest
1560+ (*WatchJobResponse)(nil), // 19: ermite.WatchJobResponse
1561+ nil, // 20: ermite.UpdateJobRequest.OutputsEntry
1562+ (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp
1563+}
1564+var file_ermite_runner_proto_depIdxs = []int32{
1565+ 3, // 0: ermite.RegisterRunnerRequest.isolation:type_name -> ermite.RunnerIsolation
1566+ 9, // 1: ermite.FetchJobResponse.job:type_name -> ermite.Job
1567+ 11, // 2: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate
1568+ 20, // 3: ermite.UpdateJobRequest.outputs:type_name -> ermite.UpdateJobRequest.OutputsEntry
1569+ 1, // 4: ermite.UpdateJobRequest.conclusion:type_name -> ermite.Conclusion
1570+ 2, // 5: ermite.StepUpdate.state:type_name -> ermite.StepState
1571+ 1, // 6: ermite.StepUpdate.conclusion:type_name -> ermite.Conclusion
1572+ 21, // 7: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp
1573+ 21, // 8: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp
1574+ 14, // 9: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine
1575+ 21, // 10: ermite.LogLine.time:type_name -> google.protobuf.Timestamp
1576+ 4, // 11: ermite.WatchJobResponse.reason:type_name -> ermite.CancelReason
1577+ 21, // 12: ermite.WatchJobResponse.requested_at:type_name -> google.protobuf.Timestamp
1578+ 5, // 13: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest
1579+ 7, // 14: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest
1580+ 10, // 15: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest
1581+ 13, // 16: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest
1582+ 16, // 17: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest
1583+ 18, // 18: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest
1584+ 6, // 19: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse
1585+ 8, // 20: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse
1586+ 12, // 21: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse
1587+ 15, // 22: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse
1588+ 17, // 23: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse
1589+ 19, // 24: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse
1590+ 19, // [19:25] is the sub-list for method output_type
1591+ 13, // [13:19] is the sub-list for method input_type
1592+ 13, // [13:13] is the sub-list for extension type_name
1593+ 13, // [13:13] is the sub-list for extension extendee
1594+ 0, // [0:13] is the sub-list for field type_name
1595+}
1596+
1597+func init() { file_ermite_runner_proto_init() }
1598+func file_ermite_runner_proto_init() {
1599+ if File_ermite_runner_proto != nil {
1600+ return
1601+ }
1602+ type x struct{}
1603+ out := protoimpl.TypeBuilder{
1604+ File: protoimpl.DescBuilder{
1605+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1606+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)),
1607+ NumEnums: 5,
1608+ NumMessages: 16,
1609+ NumExtensions: 0,
1610+ NumServices: 1,
1611+ },
1612+ GoTypes: file_ermite_runner_proto_goTypes,
1613+ DependencyIndexes: file_ermite_runner_proto_depIdxs,
1614+ EnumInfos: file_ermite_runner_proto_enumTypes,
1615+ MessageInfos: file_ermite_runner_proto_msgTypes,
1616+ }.Build()
1617+ File_ermite_runner_proto = out.File
1618+ file_ermite_runner_proto_goTypes = nil
1619+ file_ermite_runner_proto_depIdxs = nil
1620+}
M gen/ermite/server.pb.go +5 -1 generated file: collapsed
M gen_test.go +86 -0
@@ -6,6 +6,7 @@ import (
66 "testing"
77
88 "google.golang.org/protobuf/proto"
9+ "google.golang.org/protobuf/reflect/protoreflect"
910 "google.golang.org/protobuf/types/known/fieldmaskpb"
1011
1112 "bnema.dev/ermite-api/gen/ermite"
@@ -43,6 +44,21 @@ func TestProcedures(t *testing.T) {
4344 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
4445 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
4546 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
47+ {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},
48+ {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},
49+ {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},
50+ {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"},
51+ {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"},
52+ {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"},
53+ {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"},
54+ {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"},
55+ {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"},
56+ {ermiteconnect.RunServiceGetJobLogProcedure, "/ermite.RunService/GetJobLog"},
57+ {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},
58+ {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},
59+ {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},
60+ {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},
61+ {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},
4662 }
4763 for _, tt := range tests {
4864 if tt.got != tt.want {
@@ -71,6 +87,21 @@ func TestEnumNumbers(t *testing.T) {
7187 if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {
7288 t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)
7389 }
90+ if got := ermite.RunStatus_RUN_STATUS_COMPLETED; got != 3 {
91+ t.Errorf("RUN_STATUS_COMPLETED = %d, want 3", got)
92+ }
93+ if got := ermite.Conclusion_CONCLUSION_ACTION_REQUIRED; got != 7 {
94+ t.Errorf("CONCLUSION_ACTION_REQUIRED = %d, want 7", got)
95+ }
96+ if got := ermite.StepState_STEP_STATE_COMPLETED; got != 3 {
97+ t.Errorf("STEP_STATE_COMPLETED = %d, want 3", got)
98+ }
99+ if got := ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED; got != 3 {
100+ t.Errorf("RUNNER_ISOLATION_CONTAINER_PRIVILEGED = %d, want 3", got)
101+ }
102+ if got := ermite.CancelReason_CANCEL_REASON_TIMEOUT; got != 3 {
103+ t.Errorf("CANCEL_REASON_TIMEOUT = %d, want 3", got)
104+ }
74105 }
75106
76107 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.
@@ -152,3 +183,58 @@ func TestMergeRequestEventRoundTrip(t *testing.T) {
152183 t.Errorf("modes = %v", got)
153184 }
154185 }
186+
187+// TestCIEventRoundTrip checks that the CI event kinds keep their field numbers
188+// and survive the wire.
189+func TestCIEventRoundTrip(t *testing.T) {
190+ in := &ermite.WatchResponse{Event: &ermite.Event{
191+ Id: "44",
192+ Kind: &ermite.Event_JobUpdated{JobUpdated: &ermite.JobUpdated{Job: &ermite.RunJob{
193+ Id: "j1",
194+ RunId: 9,
195+ Attempt: 2,
196+ Status: ermite.RunStatus_RUN_STATUS_COMPLETED,
197+ Conclusion: ermite.Conclusion_CONCLUSION_TIMED_OUT,
198+ Isolation: ermite.RunnerIsolation_RUNNER_ISOLATION_VM,
199+ Steps: []*ermite.Step{{Number: 1, Name: "Set up job", LogStart: 0, LogEnd: 3}},
200+ }}},
201+ }}
202+ b, err := proto.Marshal(in)
203+ if err != nil {
204+ t.Fatal(err)
205+ }
206+ var out ermite.WatchResponse
207+ if err := proto.Unmarshal(b, &out); err != nil {
208+ t.Fatal(err)
209+ }
210+ if !proto.Equal(in, &out) {
211+ t.Errorf("round trip mismatch: %v != %v", &out, in)
212+ }
213+ f := (&ermite.Event{}).ProtoReflect().Descriptor().Fields()
214+ for name, num := range map[string]int{"run_updated": 12, "job_updated": 13} {
215+ if fd := f.ByName(protoreflect.Name(name)); fd == nil || int(fd.Number()) != num {
216+ t.Errorf("event field %s: %v, want number %d", name, fd, num)
217+ }
218+ }
219+}
220+
221+// TestJobRoundTrip checks the job handed to a runner survives the wire.
222+func TestJobRoundTrip(t *testing.T) {
223+ in := &ermite.FetchJobResponse{Job: &ermite.Job{
224+ JobId: "j1", RunId: 9, Attempt: 1, JobToken: "tok", WorkflowPath: ".github/workflows/ci.yml",
225+ WorkflowYaml: "on: push", JobKey: "build", Matrix: "{}", Contexts: "{}",
226+ Labels: []string{"self-hosted"}, TimeoutMinutes: 360, Repo: "ermite",
227+ Sha: "0123456789012345678901234567890123456789", Ref: "refs/heads/main",
228+ }}
229+ b, err := proto.Marshal(in)
230+ if err != nil {
231+ t.Fatal(err)
232+ }
233+ var out ermite.FetchJobResponse
234+ if err := proto.Unmarshal(b, &out); err != nil {
235+ t.Fatal(err)
236+ }
237+ if !proto.Equal(in, &out) {
238+ t.Errorf("round trip mismatch: %v != %v", &out, in)
239+ }
240+}
@@ -6,6 +6,7 @@ import (
6 "testing"6 "testing"
7 7
8 "google.golang.org/protobuf/proto"8 "google.golang.org/protobuf/proto"
9+ "google.golang.org/protobuf/reflect/protoreflect"
9 "google.golang.org/protobuf/types/known/fieldmaskpb"10 "google.golang.org/protobuf/types/known/fieldmaskpb"
10 11
11 "bnema.dev/ermite-api/gen/ermite"12 "bnema.dev/ermite-api/gen/ermite"
@@ -43,6 +44,21 @@ func TestProcedures(t *testing.T) {
43 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},44 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
44 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},45 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
45 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},46 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
47+ {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},
48+ {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},
49+ {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},
50+ {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"},
51+ {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"},
52+ {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"},
53+ {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"},
54+ {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"},
55+ {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"},
56+ {ermiteconnect.RunServiceGetJobLogProcedure, "/ermite.RunService/GetJobLog"},
57+ {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},
58+ {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},
59+ {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},
60+ {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},
61+ {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},
46 }62 }
47 for _, tt := range tests {63 for _, tt := range tests {
48 if tt.got != tt.want {64 if tt.got != tt.want {
@@ -71,6 +87,21 @@ func TestEnumNumbers(t *testing.T) {
71 if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {87 if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {
72 t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)88 t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)
73 }89 }
90+ if got := ermite.RunStatus_RUN_STATUS_COMPLETED; got != 3 {
91+ t.Errorf("RUN_STATUS_COMPLETED = %d, want 3", got)
92+ }
93+ if got := ermite.Conclusion_CONCLUSION_ACTION_REQUIRED; got != 7 {
94+ t.Errorf("CONCLUSION_ACTION_REQUIRED = %d, want 7", got)
95+ }
96+ if got := ermite.StepState_STEP_STATE_COMPLETED; got != 3 {
97+ t.Errorf("STEP_STATE_COMPLETED = %d, want 3", got)
98+ }
99+ if got := ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED; got != 3 {
100+ t.Errorf("RUNNER_ISOLATION_CONTAINER_PRIVILEGED = %d, want 3", got)
101+ }
102+ if got := ermite.CancelReason_CANCEL_REASON_TIMEOUT; got != 3 {
103+ t.Errorf("CANCEL_REASON_TIMEOUT = %d, want 3", got)
104+ }
74 }105 }
75 106
76 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.107 // TestEventRoundTrip checks that an event with a oneof kind survives the wire.
@@ -152,3 +183,58 @@ func TestMergeRequestEventRoundTrip(t *testing.T) {
152 t.Errorf("modes = %v", got)183 t.Errorf("modes = %v", got)
153 }184 }
154 }185 }
186+
187+// TestCIEventRoundTrip checks that the CI event kinds keep their field numbers
188+// and survive the wire.
189+func TestCIEventRoundTrip(t *testing.T) {
190+ in := &ermite.WatchResponse{Event: &ermite.Event{
191+ Id: "44",
192+ Kind: &ermite.Event_JobUpdated{JobUpdated: &ermite.JobUpdated{Job: &ermite.RunJob{
193+ Id: "j1",
194+ RunId: 9,
195+ Attempt: 2,
196+ Status: ermite.RunStatus_RUN_STATUS_COMPLETED,
197+ Conclusion: ermite.Conclusion_CONCLUSION_TIMED_OUT,
198+ Isolation: ermite.RunnerIsolation_RUNNER_ISOLATION_VM,
199+ Steps: []*ermite.Step{{Number: 1, Name: "Set up job", LogStart: 0, LogEnd: 3}},
200+ }}},
201+ }}
202+ b, err := proto.Marshal(in)
203+ if err != nil {
204+ t.Fatal(err)
205+ }
206+ var out ermite.WatchResponse
207+ if err := proto.Unmarshal(b, &out); err != nil {
208+ t.Fatal(err)
209+ }
210+ if !proto.Equal(in, &out) {
211+ t.Errorf("round trip mismatch: %v != %v", &out, in)
212+ }
213+ f := (&ermite.Event{}).ProtoReflect().Descriptor().Fields()
214+ for name, num := range map[string]int{"run_updated": 12, "job_updated": 13} {
215+ if fd := f.ByName(protoreflect.Name(name)); fd == nil || int(fd.Number()) != num {
216+ t.Errorf("event field %s: %v, want number %d", name, fd, num)
217+ }
218+ }
219+}
220+
221+// TestJobRoundTrip checks the job handed to a runner survives the wire.
222+func TestJobRoundTrip(t *testing.T) {
223+ in := &ermite.FetchJobResponse{Job: &ermite.Job{
224+ JobId: "j1", RunId: 9, Attempt: 1, JobToken: "tok", WorkflowPath: ".github/workflows/ci.yml",
225+ WorkflowYaml: "on: push", JobKey: "build", Matrix: "{}", Contexts: "{}",
226+ Labels: []string{"self-hosted"}, TimeoutMinutes: 360, Repo: "ermite",
227+ Sha: "0123456789012345678901234567890123456789", Ref: "refs/heads/main",
228+ }}
229+ b, err := proto.Marshal(in)
230+ if err != nil {
231+ t.Fatal(err)
232+ }
233+ var out ermite.FetchJobResponse
234+ if err := proto.Unmarshal(b, &out); err != nil {
235+ t.Fatal(err)
236+ }
237+ if !proto.Equal(in, &out) {
238+ t.Errorf("round trip mismatch: %v != %v", &out, in)
239+ }
240+}
M proto/ermite/event.proto +28 -1
@@ -6,6 +6,7 @@ package ermite;
66
77 import "ermite/merge_request.proto";
88 import "ermite/repo.proto";
9+import "ermite/run.proto";
910 import "google/protobuf/timestamp.proto";
1011
1112 // EventService streams what happens on the forge.
@@ -18,7 +19,15 @@ service EventService {
1819 // for merge requests, merge_request_opened, merge_request_merged,
1920 // merge_request_closed and comment_added. Each one is delivered on the
2021 // "repo:<name>" topic of its repository and on "home", subject to the
21- // visibility of the repository. There is no event for a change of title or
22+ // visibility of the repository.
23+ //
24+ // Servers with the feature "ci.runs" also publish run_updated and
25+ // job_updated, only on the "repo:<name>" topic of their repository (never on
26+ // "home"), subject to the same visibility. They carry the state after the
27+ // change and are published when a run or a job is created, changes status
28+ // or conclusion, or when one of its steps starts or ends (never per log
29+ // line: follow a log with RunService.GetJobLog). A job update is published
30+ // before the update of its run. There is no event for a change of title or
2231 // description, nor for a resolved comment: after a gap, or to see such
2332 // changes, clients read the merge request again. A merge publishes
2433 // merge_request_merged, then pushed for the target branch.
@@ -87,6 +96,10 @@ message Event {
8796 MergeRequestClosed merge_request_closed = 10;
8897 // CommentAdded: a comment was added to a merge request.
8998 CommentAdded comment_added = 11;
99+ // RunUpdated: a CI run was created or changed.
100+ RunUpdated run_updated = 12;
101+ // JobUpdated: a CI job was created or changed.
102+ JobUpdated job_updated = 13;
90103 }
91104 }
92105
@@ -160,3 +173,17 @@ message CommentAdded {
160173 // the state it displays (repositories, refs, merge requests and their
161174 // comments) and carry on with the next events.
162175 message Gap {}
176+
177+// RunUpdated reports a CI run that was created or whose status, conclusion or
178+// timestamps changed. It is delivered on the "repo:<name>" topic only.
179+message RunUpdated {
180+ // Run is the run after the change, as of its latest attempt, without jobs.
181+ Run run = 1;
182+}
183+
184+// JobUpdated reports a CI job that was created or whose status, conclusion,
185+// runner or steps changed. It is delivered on the "repo:<name>" topic only.
186+message JobUpdated {
187+ // Job is the job after the change, with its steps.
188+ RunJob job = 1;
189+}
@@ -6,6 +6,7 @@ package ermite;
6 6
7 import "ermite/merge_request.proto";7 import "ermite/merge_request.proto";
8 import "ermite/repo.proto";8 import "ermite/repo.proto";
9+import "ermite/run.proto";
9 import "google/protobuf/timestamp.proto";10 import "google/protobuf/timestamp.proto";
10 11
11 // EventService streams what happens on the forge.12 // EventService streams what happens on the forge.
@@ -18,7 +19,15 @@ service EventService {
18 // for merge requests, merge_request_opened, merge_request_merged,19 // for merge requests, merge_request_opened, merge_request_merged,
19 // merge_request_closed and comment_added. Each one is delivered on the20 // merge_request_closed and comment_added. Each one is delivered on the
20 // "repo:<name>" topic of its repository and on "home", subject to the21 // "repo:<name>" topic of its repository and on "home", subject to the
21- // visibility of the repository. There is no event for a change of title or22+ // visibility of the repository.
23+ //
24+ // Servers with the feature "ci.runs" also publish run_updated and
25+ // job_updated, only on the "repo:<name>" topic of their repository (never on
26+ // "home"), subject to the same visibility. They carry the state after the
27+ // change and are published when a run or a job is created, changes status
28+ // or conclusion, or when one of its steps starts or ends (never per log
29+ // line: follow a log with RunService.GetJobLog). A job update is published
30+ // before the update of its run. There is no event for a change of title or
22 // description, nor for a resolved comment: after a gap, or to see such31 // description, nor for a resolved comment: after a gap, or to see such
23 // changes, clients read the merge request again. A merge publishes32 // changes, clients read the merge request again. A merge publishes
24 // merge_request_merged, then pushed for the target branch.33 // merge_request_merged, then pushed for the target branch.
@@ -87,6 +96,10 @@ message Event {
87 MergeRequestClosed merge_request_closed = 10;96 MergeRequestClosed merge_request_closed = 10;
88 // CommentAdded: a comment was added to a merge request.97 // CommentAdded: a comment was added to a merge request.
89 CommentAdded comment_added = 11;98 CommentAdded comment_added = 11;
99+ // RunUpdated: a CI run was created or changed.
100+ RunUpdated run_updated = 12;
101+ // JobUpdated: a CI job was created or changed.
102+ JobUpdated job_updated = 13;
90 }103 }
91 }104 }
92 105
@@ -160,3 +173,17 @@ message CommentAdded {
160 // the state it displays (repositories, refs, merge requests and their173 // the state it displays (repositories, refs, merge requests and their
161 // comments) and carry on with the next events.174 // comments) and carry on with the next events.
162 message Gap {}175 message Gap {}
176+
177+// RunUpdated reports a CI run that was created or whose status, conclusion or
178+// timestamps changed. It is delivered on the "repo:<name>" topic only.
179+message RunUpdated {
180+ // Run is the run after the change, as of its latest attempt, without jobs.
181+ Run run = 1;
182+}
183+
184+// JobUpdated reports a CI job that was created or whose status, conclusion,
185+// runner or steps changed. It is delivered on the "repo:<name>" topic only.
186+message JobUpdated {
187+ // Job is the job after the change, with its steps.
188+ RunJob job = 1;
189+}
A proto/ermite/run.proto +487 -0
@@ -0,0 +1,487 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "ermite/runner.proto";
8+import "google/protobuf/timestamp.proto";
9+
10+// Run is one execution of one workflow file for one event, like a GitHub
11+// workflow run. A re-run keeps the run and adds an attempt.
12+message Run {
13+ // Repo is the name of the repository.
14+ string repo = 1;
15+ // Id identifies the run on the whole server: it is the "github.run_id".
16+ // Increasing, never reused.
17+ int64 id = 2;
18+ // Number counts the runs of the same workflow file in the repository,
19+ // starting at 1, never reused: it is the "github.run_number". It does not
20+ // change on a re-run.
21+ int64 number = 3;
22+ // Attempt is the attempt this message describes: 1 for the first, one more
23+ // after each RerunRun. ListRuns and events describe the latest attempt;
24+ // GetRun can ask for another one.
25+ int32 attempt = 4;
26+ // WorkflowPath is the path of the workflow file at sha, for example
27+ // ".github/workflows/ci.yml".
28+ string workflow_path = 5;
29+ // WorkflowName is the "name" of the workflow, or workflow_path when it has
30+ // none (as GitHub does).
31+ string workflow_name = 6;
32+ // Title is the first line of the message of the commit sha, at most 256
33+ // characters (GitHub's display title).
34+ string title = 7;
35+ // Event is the event that triggered the run, in GitHub's name: "push" (the
36+ // only one for now; push to a branch or a tag, and merge of a merge
37+ // request). New values may be added; clients show the ones they do not
38+ // know as they are.
39+ string event = 8;
40+ // Ref is the full ref name that was pushed, for example "refs/heads/main"
41+ // or "refs/tags/v1.0.0".
42+ string ref = 9;
43+ // Sha is the full id of the commit the run is for.
44+ string sha = 10;
45+ // Actor names who triggered the run: the label of the token that pushed, or
46+ // "root", or "owner" (a web session, a merge of the web UI), as
47+ // MergeRequest.author. It is informational.
48+ string actor = 11;
49+ // Status is where this attempt is: QUEUED until a job starts, IN_PROGRESS
50+ // while jobs run or wait, COMPLETED when every job is over.
51+ RunStatus status = 12;
52+ // Conclusion is how this attempt ended, from its jobs' conclusions like
53+ // GitHub: CANCELLED if it was cancelled, else FAILURE if a job failed or
54+ // timed out, else SUCCESS (also when jobs were skipped). Unset until
55+ // status is COMPLETED.
56+ Conclusion conclusion = 13;
57+ // Error is set when the run failed without running a job because the
58+ // workflow could not be used: an invalid workflow file (the run then has
59+ // no jobs and conclusion FAILURE), as GitHub's "Invalid workflow file".
60+ // It holds the errors with their positions, one per line, at most 8 KiB.
61+ // Empty otherwise.
62+ string error = 14;
63+ // CreatedAt is when this attempt was created: for attempt 1 when the
64+ // trigger was planned, for later ones when RerunRun was called.
65+ google.protobuf.Timestamp created_at = 15;
66+ // StartedAt is when the first job of this attempt was given to a runner.
67+ // Unset until then.
68+ google.protobuf.Timestamp started_at = 16;
69+ // CompletedAt is when this attempt completed. Unset until then.
70+ google.protobuf.Timestamp completed_at = 17;
71+}
72+
73+// RunJob is a job of a run as people see it: the state of one job instance
74+// (one matrix combination is one job) in one attempt. It is not Job, which is
75+// what a runner receives.
76+message RunJob {
77+ // Id is the Job.job_id. It is the same in every attempt.
78+ string id = 1;
79+ // RunId is the run the job belongs to.
80+ int64 run_id = 2;
81+ // Attempt is the attempt of the run in which this job executed. It is lower
82+ // than the attempt of the run when the job was kept from an earlier attempt
83+ // by a re-run of the failed jobs only; the log and steps are then those of
84+ // that earlier attempt. Pass it to GetJob and GetJobLog.
85+ int32 attempt = 3;
86+ // Repo is the name of the repository.
87+ string repo = 4;
88+ // Key is the key of the job under "jobs:" in the workflow (the
89+ // "github.job"). Matrix instances of one job share it.
90+ string key = 5;
91+ // Name is the display name of the job, evaluated, with the matrix suffix as
92+ // GitHub names them, for example "build (linux, 1.27)". It is also the
93+ // name of the job's check.
94+ string name = 6;
95+ // Status is where the job is. A job waits QUEUED for a runner and for the
96+ // jobs it needs.
97+ RunStatus status = 7;
98+ // Conclusion is how the job ended. Unset until status is COMPLETED.
99+ Conclusion conclusion = 8;
100+ // Error explains a FAILURE, CANCELLED or TIMED_OUT that no step explains, in
101+ // GitHub's words where it has them: the runner was lost ("The self-hosted
102+ // runner: <name> lost communication with the server..."), it received a
103+ // shutdown signal, no runner took the job within 24 hours, the log limit
104+ // was reached, the workflow uses a feature that is not supported yet
105+ // ("not supported yet: <feature>"). At most 4 KiB. Empty otherwise.
106+ string error = 9;
107+ // Labels are the runs-on labels of the job, evaluated. Empty while the job
108+ // is not planned yet.
109+ repeated string labels = 10;
110+ // Needs are the keys of the jobs this job needs, for drawing the graph.
111+ repeated string needs = 11;
112+ // RunnerId is the id of the runner that executed or is executing the job.
113+ // Empty while queued, and when the runner has since been removed (runner_name
114+ // and isolation are kept).
115+ string runner_id = 12;
116+ // RunnerName is the name of that runner. Empty while queued.
117+ string runner_name = 13;
118+ // Isolation is how that runner isolated the job. UNSPECIFIED while queued.
119+ RunnerIsolation isolation = 14;
120+ // Steps are the steps reported so far, ordered by number, including the
121+ // implicit "Set up job" and "Complete job". Empty while queued. Always
122+ // set, including in JobUpdated events.
123+ repeated Step steps = 15;
124+ // CreatedAt is when the job was created (planned).
125+ google.protobuf.Timestamp created_at = 16;
126+ // StartedAt is when the job was given to a runner. Unset until then.
127+ google.protobuf.Timestamp started_at = 17;
128+ // CompletedAt is when the job completed. Unset until then.
129+ google.protobuf.Timestamp completed_at = 18;
130+}
131+
132+// Step is one step of a job, with where its lines are in the job log.
133+message Step {
134+ // Number is the 1-based number of the step in the job, the one the runner
135+ // uses in UpdateJob and AppendLog.
136+ int32 number = 1;
137+ // Name is the display name of the step.
138+ string name = 2;
139+ // State is where the step is.
140+ StepState state = 3;
141+ // Conclusion is how the step ended. Unset until state is COMPLETED.
142+ Conclusion conclusion = 4;
143+ // StartedAt is when the step started. Unset while PENDING.
144+ google.protobuf.Timestamp started_at = 5;
145+ // CompletedAt is when the step ended. Unset until COMPLETED.
146+ google.protobuf.Timestamp completed_at = 6;
147+ // LogStart is the number of the first line of the step in the job log (the
148+ // offset of GetJobLogRequest and AppendLogRequest), counted from 0.
149+ int64 log_start = 7;
150+ // LogEnd is the number of the line after the last one of the step. For an
151+ // IN_PROGRESS step it is the number of lines held at the time of the
152+ // call and grows; for a step without a line it equals log_start.
153+ int64 log_end = 8;
154+}
155+
156+// Check is the result of one job on one commit, the unit shown next to a
157+// commit or a merge request and counted by the merge rules. There is one per
158+// job instance, named like GitHub's checks.
159+message Check {
160+ // Id identifies the check. It is opaque and stable across attempts.
161+ string id = 1;
162+ // Repo is the name of the repository.
163+ string repo = 2;
164+ // Sha is the full id of the commit.
165+ string sha = 3;
166+ // Name is the name of the check: the RunJob.name of the job.
167+ string name = 4;
168+ // Status is where the check is.
169+ RunStatus status = 5;
170+ // Conclusion is how the check ended. Unset until status is COMPLETED. The
171+ // value "stale" of GitHub is never used.
172+ Conclusion conclusion = 6;
173+ // RunId is the run of the job.
174+ int64 run_id = 7;
175+ // JobId is the RunJob.id of the job, to open its page and log.
176+ string job_id = 8;
177+ // Attempt is the attempt the check describes: the latest attempt of the run.
178+ int32 attempt = 9;
179+ // WorkflowName is the Run.workflow_name of the run.
180+ string workflow_name = 10;
181+ // StartedAt is when the job was given to a runner. Unset while queued.
182+ google.protobuf.Timestamp started_at = 11;
183+ // CompletedAt is when the job completed. Unset until then.
184+ google.protobuf.Timestamp completed_at = 12;
185+}
186+
187+// Runner is a registered CI runner. It never contains the runner token nor its
188+// hash.
189+message Runner {
190+ // Id identifies the runner in RemoveRunner. It is not a secret.
191+ string id = 1;
192+ // Name is the unique name chosen at registration.
193+ string name = 2;
194+ // Labels are all the labels the runner can match: the ones chosen at
195+ // registration plus the implicit ones ("self-hosted", the os and the arch),
196+ // lowercase, sorted.
197+ repeated string labels = 3;
198+ // Isolation is how the runner isolates jobs.
199+ RunnerIsolation isolation = 4;
200+ // Version is the version of the runner software.
201+ string version = 5;
202+ // Arch is "x64" or "arm64".
203+ string arch = 6;
204+ // Os is "linux".
205+ string os = 7;
206+ // RegisteredAt is when the runner was registered.
207+ google.protobuf.Timestamp registered_at = 8;
208+ // LastSeen is when the server last heard from the runner: a FetchJob poll or
209+ // a heartbeat of one of its jobs. Unset if never. It may lag behind the real
210+ // time by a short delay. A runner not seen for more than about 2 minutes is
211+ // offline.
212+ google.protobuf.Timestamp last_seen = 9;
213+ // Busy is true while the runner has a job in progress.
214+ bool busy = 10;
215+}
216+
217+// RunService reads and controls CI runs, like `gh run` does on GitHub. It is for
218+// owners and their tools; runners use RunnerService.
219+//
220+// Runs, jobs, checks and logs belong to a repository, and reading them needs
221+// the same access as reading it: nothing for a public repository, the
222+// repo:read scope for a private one. A repository that does not exist or that
223+// the caller cannot read gives not_found; so does a run or a job that belongs
224+// to another repository than the one named (ids are global but are always
225+// checked against the repository). Cancelling and re-running need the ci:write
226+// scope on the repository (a token restricted to other repositories gets
227+// permission_denied). The runner RPCs (ListRunners, RemoveRunner) are not about
228+// a repository and need the runner:register scope on a token that is not
229+// restricted to repositories, or the root token. A caller without the needed
230+// scope gets permission_denied.
231+//
232+// Page sizes and page tokens work as in the other services: page_size 0 is the
233+// server default, larger values are lowered to the server maximum, and a
234+// page_token is the next_page_token of a previous response for the same
235+// request (other fields must be unchanged).
236+//
237+// Servers that implement the runs side (every RPC but ListRunners and
238+// RemoveRunner) list "ci.runs" in GetVersionResponse.features; those that
239+// implement the runners side list "ci.runners", as RunnerService does. A server
240+// that honours the filters of ListRunsRequest also lists "ci.runs.filters".
241+service RunService {
242+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
243+ // run is described by its latest attempt. The filters narrow the result; a
244+ // server without the feature "ci.runs.filters" ignores them and returns
245+ // every run, so a client that needs them checks the feature. Fails with
246+ // invalid_argument if a filter is malformed.
247+ rpc ListRuns(ListRunsRequest) returns (ListRunsResponse);
248+ // GetRun returns a run with its jobs and their steps, for the latest attempt
249+ // or for the one asked. Fails with not_found if the run or the attempt does
250+ // not exist.
251+ rpc GetRun(GetRunRequest) returns (GetRunResponse);
252+ // GetJob returns one job with its steps. Fails with not_found if the job
253+ // or the attempt does not exist.
254+ rpc GetJob(GetJobRequest) returns (GetJobResponse);
255+ // GetJobLog streams the lines of the log of a job, from a line number. The
256+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
257+ // text each.
258+ //
259+ // Without follow, the stream ends after the last line stored when the call
260+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
261+ // delay after the runner's AppendLog, and ends without error once the job has
262+ // completed and all its lines were sent; on a completed job follow changes
263+ // nothing. A client that loses the stream calls again with the offset after
264+ // the last line it received. The client may cancel at any time.
265+ //
266+ // Fails with not_found if the job, the attempt or its log does not exist (the
267+ // log is removed by the retention sweep, and a queued job has none: with
268+ // follow, the call waits for the first line instead), with out_of_range if
269+ // offset is beyond the end of the log of a job without follow, and with
270+ // resource_exhausted when there are too many concurrent log streams, in
271+ // total or from one client. Like Watch, a stream ends with unauthenticated
272+ // when the token was revoked or expired.
273+ rpc GetJobLog(GetJobLogRequest) returns (stream GetJobLogResponse);
274+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
275+ // scope. Jobs that have not started are cancelled at once; jobs in progress
276+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
277+ // when they have ended, so the returned run may still be IN_PROGRESS.
278+ // Cancelling a run that already has a cancellation requested succeeds and
279+ // changes nothing. Fails with failed_precondition if the run has completed.
280+ rpc CancelRun(CancelRunRequest) returns (CancelRunResponse);
281+ // RerunRun starts a new attempt of a completed run: same run id and number,
282+ // attempt + 1, same commit and same workflow file as at the original run (the
283+ // file of the repository is not read again). Needs the ci:write scope. Fails
284+ // with failed_precondition if the latest attempt has not completed, or if
285+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
286+ rpc RerunRun(RerunRunRequest) returns (RerunRunResponse);
287+ // ListChecks lists the checks of a commit: for each job of each run on that
288+ // sha, the one of the latest attempt, ordered by creation time then name.
289+ // A commit without a run has no check: the list is empty, which is not an
290+ // error, and the server does not verify that the commit exists. Returns at
291+ // most 1,000 checks (truncated tells).
292+ rpc ListChecks(ListChecksRequest) returns (ListChecksResponse);
293+ // ListRunners lists the registered runners, ordered by name. Needs the
294+ // runner:register scope.
295+ rpc ListRunners(ListRunnersRequest) returns (ListRunnersResponse);
296+ // RemoveRunner removes a runner: its runner token stops working at once (a
297+ // FetchJob in progress ends with unauthenticated) and it no longer gets
298+ // jobs. A job it is running is not affected: it ends normally, or fails as
299+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
300+ // with not_found if the id is unknown.
301+ rpc RemoveRunner(RemoveRunnerRequest) returns (RemoveRunnerResponse);
302+}
303+
304+// ListRunsRequest selects a page of runs.
305+message ListRunsRequest {
306+ // Repo is the repository name.
307+ string repo = 1;
308+ // PageSize is the maximum number of runs to return.
309+ int32 page_size = 2;
310+ // PageToken is the next_page_token of a previous response. Empty for the
311+ // first page.
312+ string page_token = 3;
313+ // Workflow restricts the result to one workflow file: its path
314+ // (".github/workflows/ci.yml") or its file name ("ci.yml"). Empty means
315+ // every workflow. Ignored by servers without "ci.runs.filters".
316+ string workflow = 4;
317+ // Branch restricts the result to the runs whose ref is this branch, by
318+ // short name ("main"). Empty means every ref. Ignored by servers without
319+ // "ci.runs.filters".
320+ string branch = 5;
321+ // Status restricts the result to the runs whose latest attempt has this
322+ // status. RUN_STATUS_UNSPECIFIED means every status. Ignored by servers
323+ // without "ci.runs.filters".
324+ RunStatus status = 6;
325+}
326+
327+// ListRunsResponse is one page of runs.
328+message ListRunsResponse {
329+ // Runs are the runs of this page, highest id first, without jobs.
330+ repeated Run runs = 1;
331+ // NextPageToken is passed as page_token to get the next page. Empty when
332+ // there is no next page.
333+ string next_page_token = 2;
334+}
335+
336+// GetRunRequest selects a run.
337+message GetRunRequest {
338+ // Repo is the repository name.
339+ string repo = 1;
340+ // RunId is the Run.id.
341+ int64 run_id = 2;
342+ // Attempt is the attempt to read. 0 means the latest.
343+ int32 attempt = 3;
344+}
345+
346+// GetRunResponse holds a run with its jobs.
347+message GetRunResponse {
348+ // Run is the run, as of the attempt asked.
349+ Run run = 1;
350+ // Jobs are the jobs of that attempt, with their steps, in the order of the
351+ // workflow file (matrix instances in the order of the matrix). Jobs kept from
352+ // an earlier attempt by a re-run of failed jobs are included, with their own
353+ // RunJob.attempt. Empty for a run that failed on an invalid workflow file.
354+ repeated RunJob jobs = 2;
355+ // LatestAttempt is the latest attempt of the run, to offer the other ones.
356+ int32 latest_attempt = 3;
357+}
358+
359+// GetJobRequest selects a job.
360+message GetJobRequest {
361+ // Repo is the repository name.
362+ string repo = 1;
363+ // JobId is the RunJob.id.
364+ string job_id = 2;
365+ // Attempt is the RunJob.attempt to read. 0 means the latest.
366+ int32 attempt = 3;
367+}
368+
369+// GetJobResponse holds a job.
370+message GetJobResponse {
371+ // Job is the job with its steps.
372+ RunJob job = 1;
373+}
374+
375+// GetJobLogRequest selects the lines to stream.
376+message GetJobLogRequest {
377+ // Repo is the repository name.
378+ string repo = 1;
379+ // JobId is the RunJob.id.
380+ string job_id = 2;
381+ // Attempt is the RunJob.attempt whose log to read. 0 means the latest.
382+ int32 attempt = 3;
383+ // Offset is the number of the first line to send, counted from 0 across all
384+ // steps (see Step.log_start). Negative is invalid_argument.
385+ int64 offset = 4;
386+ // Follow keeps the stream open while the job runs. See GetJobLog.
387+ bool follow = 5;
388+}
389+
390+// GetJobLogResponse is one message of the log stream.
391+message GetJobLogResponse {
392+ // Offset is the number of the first line of lines.
393+ int64 offset = 1;
394+ // Lines are consecutive lines of the log, at most 1,000, at most 64 KiB of
395+ // text in total. Never empty.
396+ repeated JobLogLine lines = 2;
397+}
398+
399+// JobLogLine is one line of a job log, as the runner sent it.
400+message JobLogLine {
401+ // Step is the number of the step the line belongs to.
402+ int32 step = 1;
403+ // Time is when the runner produced the line, with nanosecond precision.
404+ google.protobuf.Timestamp time = 2;
405+ // Text is the line without its terminator, as in LogLine.text, including the
406+ // "##[group]" and "##[endgroup]" markers. Clients show it escaped.
407+ string text = 3;
408+}
409+
410+// CancelRunRequest names the run to cancel.
411+message CancelRunRequest {
412+ // Repo is the repository name.
413+ string repo = 1;
414+ // RunId is the Run.id.
415+ int64 run_id = 2;
416+}
417+
418+// CancelRunResponse holds the run after the request.
419+message CancelRunResponse {
420+ // Run is the latest attempt of the run.
421+ Run run = 1;
422+}
423+
424+// RerunRunRequest names the run to run again.
425+message RerunRunRequest {
426+ // Repo is the repository name.
427+ string repo = 1;
428+ // RunId is the Run.id.
429+ int64 run_id = 2;
430+ // FailedOnly runs again only the jobs of the latest attempt that did not
431+ // succeed (conclusion FAILURE, CANCELLED, TIMED_OUT or ACTION_REQUIRED) and
432+ // the jobs that need them, directly or not, even if they were skipped. The
433+ // other jobs keep their result, steps and log from the earlier attempt. When
434+ // false, every job runs again.
435+ bool failed_only = 3;
436+}
437+
438+// RerunRunResponse holds the new attempt.
439+message RerunRunResponse {
440+ // Run is the run as of the new attempt, QUEUED.
441+ Run run = 1;
442+}
443+
444+// ListChecksRequest names the commit.
445+message ListChecksRequest {
446+ // Repo is the repository name.
447+ string repo = 1;
448+ // Sha is the full commit id (40 lowercase hexadecimal characters for SHA-1).
449+ // Abbreviations and names are invalid_argument.
450+ string sha = 2;
451+}
452+
453+// ListChecksResponse holds the checks of a commit.
454+message ListChecksResponse {
455+ // Checks are the checks of the commit, one per job of the latest attempt of
456+ // each run, ordered by creation time then name.
457+ repeated Check checks = 1;
458+ // Truncated is true when the commit has more checks than checks holds.
459+ bool truncated = 2;
460+}
461+
462+// ListRunnersRequest selects a page of runners.
463+message ListRunnersRequest {
464+ // PageSize is the maximum number of runners to return.
465+ int32 page_size = 1;
466+ // PageToken is the next_page_token of a previous response. Empty for the
467+ // first page.
468+ string page_token = 2;
469+}
470+
471+// ListRunnersResponse is one page of runners.
472+message ListRunnersResponse {
473+ // Runners are the runners of this page, ordered by name.
474+ repeated Runner runners = 1;
475+ // NextPageToken is passed as page_token to get the next page. Empty when
476+ // there is no next page.
477+ string next_page_token = 2;
478+}
479+
480+// RemoveRunnerRequest names the runner to remove.
481+message RemoveRunnerRequest {
482+ // RunnerId is the Runner.id.
483+ string runner_id = 1;
484+}
485+
486+// RemoveRunnerResponse is empty.
487+message RemoveRunnerResponse {}
@@ -0,0 +1,487 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "ermite/runner.proto";
8+import "google/protobuf/timestamp.proto";
9+
10+// Run is one execution of one workflow file for one event, like a GitHub
11+// workflow run. A re-run keeps the run and adds an attempt.
12+message Run {
13+ // Repo is the name of the repository.
14+ string repo = 1;
15+ // Id identifies the run on the whole server: it is the "github.run_id".
16+ // Increasing, never reused.
17+ int64 id = 2;
18+ // Number counts the runs of the same workflow file in the repository,
19+ // starting at 1, never reused: it is the "github.run_number". It does not
20+ // change on a re-run.
21+ int64 number = 3;
22+ // Attempt is the attempt this message describes: 1 for the first, one more
23+ // after each RerunRun. ListRuns and events describe the latest attempt;
24+ // GetRun can ask for another one.
25+ int32 attempt = 4;
26+ // WorkflowPath is the path of the workflow file at sha, for example
27+ // ".github/workflows/ci.yml".
28+ string workflow_path = 5;
29+ // WorkflowName is the "name" of the workflow, or workflow_path when it has
30+ // none (as GitHub does).
31+ string workflow_name = 6;
32+ // Title is the first line of the message of the commit sha, at most 256
33+ // characters (GitHub's display title).
34+ string title = 7;
35+ // Event is the event that triggered the run, in GitHub's name: "push" (the
36+ // only one for now; push to a branch or a tag, and merge of a merge
37+ // request). New values may be added; clients show the ones they do not
38+ // know as they are.
39+ string event = 8;
40+ // Ref is the full ref name that was pushed, for example "refs/heads/main"
41+ // or "refs/tags/v1.0.0".
42+ string ref = 9;
43+ // Sha is the full id of the commit the run is for.
44+ string sha = 10;
45+ // Actor names who triggered the run: the label of the token that pushed, or
46+ // "root", or "owner" (a web session, a merge of the web UI), as
47+ // MergeRequest.author. It is informational.
48+ string actor = 11;
49+ // Status is where this attempt is: QUEUED until a job starts, IN_PROGRESS
50+ // while jobs run or wait, COMPLETED when every job is over.
51+ RunStatus status = 12;
52+ // Conclusion is how this attempt ended, from its jobs' conclusions like
53+ // GitHub: CANCELLED if it was cancelled, else FAILURE if a job failed or
54+ // timed out, else SUCCESS (also when jobs were skipped). Unset until
55+ // status is COMPLETED.
56+ Conclusion conclusion = 13;
57+ // Error is set when the run failed without running a job because the
58+ // workflow could not be used: an invalid workflow file (the run then has
59+ // no jobs and conclusion FAILURE), as GitHub's "Invalid workflow file".
60+ // It holds the errors with their positions, one per line, at most 8 KiB.
61+ // Empty otherwise.
62+ string error = 14;
63+ // CreatedAt is when this attempt was created: for attempt 1 when the
64+ // trigger was planned, for later ones when RerunRun was called.
65+ google.protobuf.Timestamp created_at = 15;
66+ // StartedAt is when the first job of this attempt was given to a runner.
67+ // Unset until then.
68+ google.protobuf.Timestamp started_at = 16;
69+ // CompletedAt is when this attempt completed. Unset until then.
70+ google.protobuf.Timestamp completed_at = 17;
71+}
72+
73+// RunJob is a job of a run as people see it: the state of one job instance
74+// (one matrix combination is one job) in one attempt. It is not Job, which is
75+// what a runner receives.
76+message RunJob {
77+ // Id is the Job.job_id. It is the same in every attempt.
78+ string id = 1;
79+ // RunId is the run the job belongs to.
80+ int64 run_id = 2;
81+ // Attempt is the attempt of the run in which this job executed. It is lower
82+ // than the attempt of the run when the job was kept from an earlier attempt
83+ // by a re-run of the failed jobs only; the log and steps are then those of
84+ // that earlier attempt. Pass it to GetJob and GetJobLog.
85+ int32 attempt = 3;
86+ // Repo is the name of the repository.
87+ string repo = 4;
88+ // Key is the key of the job under "jobs:" in the workflow (the
89+ // "github.job"). Matrix instances of one job share it.
90+ string key = 5;
91+ // Name is the display name of the job, evaluated, with the matrix suffix as
92+ // GitHub names them, for example "build (linux, 1.27)". It is also the
93+ // name of the job's check.
94+ string name = 6;
95+ // Status is where the job is. A job waits QUEUED for a runner and for the
96+ // jobs it needs.
97+ RunStatus status = 7;
98+ // Conclusion is how the job ended. Unset until status is COMPLETED.
99+ Conclusion conclusion = 8;
100+ // Error explains a FAILURE, CANCELLED or TIMED_OUT that no step explains, in
101+ // GitHub's words where it has them: the runner was lost ("The self-hosted
102+ // runner: <name> lost communication with the server..."), it received a
103+ // shutdown signal, no runner took the job within 24 hours, the log limit
104+ // was reached, the workflow uses a feature that is not supported yet
105+ // ("not supported yet: <feature>"). At most 4 KiB. Empty otherwise.
106+ string error = 9;
107+ // Labels are the runs-on labels of the job, evaluated. Empty while the job
108+ // is not planned yet.
109+ repeated string labels = 10;
110+ // Needs are the keys of the jobs this job needs, for drawing the graph.
111+ repeated string needs = 11;
112+ // RunnerId is the id of the runner that executed or is executing the job.
113+ // Empty while queued, and when the runner has since been removed (runner_name
114+ // and isolation are kept).
115+ string runner_id = 12;
116+ // RunnerName is the name of that runner. Empty while queued.
117+ string runner_name = 13;
118+ // Isolation is how that runner isolated the job. UNSPECIFIED while queued.
119+ RunnerIsolation isolation = 14;
120+ // Steps are the steps reported so far, ordered by number, including the
121+ // implicit "Set up job" and "Complete job". Empty while queued. Always
122+ // set, including in JobUpdated events.
123+ repeated Step steps = 15;
124+ // CreatedAt is when the job was created (planned).
125+ google.protobuf.Timestamp created_at = 16;
126+ // StartedAt is when the job was given to a runner. Unset until then.
127+ google.protobuf.Timestamp started_at = 17;
128+ // CompletedAt is when the job completed. Unset until then.
129+ google.protobuf.Timestamp completed_at = 18;
130+}
131+
132+// Step is one step of a job, with where its lines are in the job log.
133+message Step {
134+ // Number is the 1-based number of the step in the job, the one the runner
135+ // uses in UpdateJob and AppendLog.
136+ int32 number = 1;
137+ // Name is the display name of the step.
138+ string name = 2;
139+ // State is where the step is.
140+ StepState state = 3;
141+ // Conclusion is how the step ended. Unset until state is COMPLETED.
142+ Conclusion conclusion = 4;
143+ // StartedAt is when the step started. Unset while PENDING.
144+ google.protobuf.Timestamp started_at = 5;
145+ // CompletedAt is when the step ended. Unset until COMPLETED.
146+ google.protobuf.Timestamp completed_at = 6;
147+ // LogStart is the number of the first line of the step in the job log (the
148+ // offset of GetJobLogRequest and AppendLogRequest), counted from 0.
149+ int64 log_start = 7;
150+ // LogEnd is the number of the line after the last one of the step. For an
151+ // IN_PROGRESS step it is the number of lines held at the time of the
152+ // call and grows; for a step without a line it equals log_start.
153+ int64 log_end = 8;
154+}
155+
156+// Check is the result of one job on one commit, the unit shown next to a
157+// commit or a merge request and counted by the merge rules. There is one per
158+// job instance, named like GitHub's checks.
159+message Check {
160+ // Id identifies the check. It is opaque and stable across attempts.
161+ string id = 1;
162+ // Repo is the name of the repository.
163+ string repo = 2;
164+ // Sha is the full id of the commit.
165+ string sha = 3;
166+ // Name is the name of the check: the RunJob.name of the job.
167+ string name = 4;
168+ // Status is where the check is.
169+ RunStatus status = 5;
170+ // Conclusion is how the check ended. Unset until status is COMPLETED. The
171+ // value "stale" of GitHub is never used.
172+ Conclusion conclusion = 6;
173+ // RunId is the run of the job.
174+ int64 run_id = 7;
175+ // JobId is the RunJob.id of the job, to open its page and log.
176+ string job_id = 8;
177+ // Attempt is the attempt the check describes: the latest attempt of the run.
178+ int32 attempt = 9;
179+ // WorkflowName is the Run.workflow_name of the run.
180+ string workflow_name = 10;
181+ // StartedAt is when the job was given to a runner. Unset while queued.
182+ google.protobuf.Timestamp started_at = 11;
183+ // CompletedAt is when the job completed. Unset until then.
184+ google.protobuf.Timestamp completed_at = 12;
185+}
186+
187+// Runner is a registered CI runner. It never contains the runner token nor its
188+// hash.
189+message Runner {
190+ // Id identifies the runner in RemoveRunner. It is not a secret.
191+ string id = 1;
192+ // Name is the unique name chosen at registration.
193+ string name = 2;
194+ // Labels are all the labels the runner can match: the ones chosen at
195+ // registration plus the implicit ones ("self-hosted", the os and the arch),
196+ // lowercase, sorted.
197+ repeated string labels = 3;
198+ // Isolation is how the runner isolates jobs.
199+ RunnerIsolation isolation = 4;
200+ // Version is the version of the runner software.
201+ string version = 5;
202+ // Arch is "x64" or "arm64".
203+ string arch = 6;
204+ // Os is "linux".
205+ string os = 7;
206+ // RegisteredAt is when the runner was registered.
207+ google.protobuf.Timestamp registered_at = 8;
208+ // LastSeen is when the server last heard from the runner: a FetchJob poll or
209+ // a heartbeat of one of its jobs. Unset if never. It may lag behind the real
210+ // time by a short delay. A runner not seen for more than about 2 minutes is
211+ // offline.
212+ google.protobuf.Timestamp last_seen = 9;
213+ // Busy is true while the runner has a job in progress.
214+ bool busy = 10;
215+}
216+
217+// RunService reads and controls CI runs, like `gh run` does on GitHub. It is for
218+// owners and their tools; runners use RunnerService.
219+//
220+// Runs, jobs, checks and logs belong to a repository, and reading them needs
221+// the same access as reading it: nothing for a public repository, the
222+// repo:read scope for a private one. A repository that does not exist or that
223+// the caller cannot read gives not_found; so does a run or a job that belongs
224+// to another repository than the one named (ids are global but are always
225+// checked against the repository). Cancelling and re-running need the ci:write
226+// scope on the repository (a token restricted to other repositories gets
227+// permission_denied). The runner RPCs (ListRunners, RemoveRunner) are not about
228+// a repository and need the runner:register scope on a token that is not
229+// restricted to repositories, or the root token. A caller without the needed
230+// scope gets permission_denied.
231+//
232+// Page sizes and page tokens work as in the other services: page_size 0 is the
233+// server default, larger values are lowered to the server maximum, and a
234+// page_token is the next_page_token of a previous response for the same
235+// request (other fields must be unchanged).
236+//
237+// Servers that implement the runs side (every RPC but ListRunners and
238+// RemoveRunner) list "ci.runs" in GetVersionResponse.features; those that
239+// implement the runners side list "ci.runners", as RunnerService does. A server
240+// that honours the filters of ListRunsRequest also lists "ci.runs.filters".
241+service RunService {
242+ // ListRuns lists the runs of a repository, highest id (newest) first. Each
243+ // run is described by its latest attempt. The filters narrow the result; a
244+ // server without the feature "ci.runs.filters" ignores them and returns
245+ // every run, so a client that needs them checks the feature. Fails with
246+ // invalid_argument if a filter is malformed.
247+ rpc ListRuns(ListRunsRequest) returns (ListRunsResponse);
248+ // GetRun returns a run with its jobs and their steps, for the latest attempt
249+ // or for the one asked. Fails with not_found if the run or the attempt does
250+ // not exist.
251+ rpc GetRun(GetRunRequest) returns (GetRunResponse);
252+ // GetJob returns one job with its steps. Fails with not_found if the job
253+ // or the attempt does not exist.
254+ rpc GetJob(GetJobRequest) returns (GetJobResponse);
255+ // GetJobLog streams the lines of the log of a job, from a line number. The
256+ // server sends them in order in messages of at most 1,000 lines and 64 KiB of
257+ // text each.
258+ //
259+ // Without follow, the stream ends after the last line stored when the call
260+ // was made. With follow, it goes on as lines arrive, with at most 1 second of
261+ // delay after the runner's AppendLog, and ends without error once the job has
262+ // completed and all its lines were sent; on a completed job follow changes
263+ // nothing. A client that loses the stream calls again with the offset after
264+ // the last line it received. The client may cancel at any time.
265+ //
266+ // Fails with not_found if the job, the attempt or its log does not exist (the
267+ // log is removed by the retention sweep, and a queued job has none: with
268+ // follow, the call waits for the first line instead), with out_of_range if
269+ // offset is beyond the end of the log of a job without follow, and with
270+ // resource_exhausted when there are too many concurrent log streams, in
271+ // total or from one client. Like Watch, a stream ends with unauthenticated
272+ // when the token was revoked or expired.
273+ rpc GetJobLog(GetJobLogRequest) returns (stream GetJobLogResponse);
274+ // CancelRun asks to cancel the latest attempt of a run. Needs the ci:write
275+ // scope. Jobs that have not started are cancelled at once; jobs in progress
276+ // get a CANCEL_REASON_USER request through WatchJob, and the run completes
277+ // when they have ended, so the returned run may still be IN_PROGRESS.
278+ // Cancelling a run that already has a cancellation requested succeeds and
279+ // changes nothing. Fails with failed_precondition if the run has completed.
280+ rpc CancelRun(CancelRunRequest) returns (CancelRunResponse);
281+ // RerunRun starts a new attempt of a completed run: same run id and number,
282+ // attempt + 1, same commit and same workflow file as at the original run (the
283+ // file of the repository is not read again). Needs the ci:write scope. Fails
284+ // with failed_precondition if the latest attempt has not completed, or if
285+ // failed_only is set and no job failed. The new attempt is returned QUEUED.
286+ rpc RerunRun(RerunRunRequest) returns (RerunRunResponse);
287+ // ListChecks lists the checks of a commit: for each job of each run on that
288+ // sha, the one of the latest attempt, ordered by creation time then name.
289+ // A commit without a run has no check: the list is empty, which is not an
290+ // error, and the server does not verify that the commit exists. Returns at
291+ // most 1,000 checks (truncated tells).
292+ rpc ListChecks(ListChecksRequest) returns (ListChecksResponse);
293+ // ListRunners lists the registered runners, ordered by name. Needs the
294+ // runner:register scope.
295+ rpc ListRunners(ListRunnersRequest) returns (ListRunnersResponse);
296+ // RemoveRunner removes a runner: its runner token stops working at once (a
297+ // FetchJob in progress ends with unauthenticated) and it no longer gets
298+ // jobs. A job it is running is not affected: it ends normally, or fails as
299+ // a lost runner without heartbeats. Needs the runner:register scope. Fails
300+ // with not_found if the id is unknown.
301+ rpc RemoveRunner(RemoveRunnerRequest) returns (RemoveRunnerResponse);
302+}
303+
304+// ListRunsRequest selects a page of runs.
305+message ListRunsRequest {
306+ // Repo is the repository name.
307+ string repo = 1;
308+ // PageSize is the maximum number of runs to return.
309+ int32 page_size = 2;
310+ // PageToken is the next_page_token of a previous response. Empty for the
311+ // first page.
312+ string page_token = 3;
313+ // Workflow restricts the result to one workflow file: its path
314+ // (".github/workflows/ci.yml") or its file name ("ci.yml"). Empty means
315+ // every workflow. Ignored by servers without "ci.runs.filters".
316+ string workflow = 4;
317+ // Branch restricts the result to the runs whose ref is this branch, by
318+ // short name ("main"). Empty means every ref. Ignored by servers without
319+ // "ci.runs.filters".
320+ string branch = 5;
321+ // Status restricts the result to the runs whose latest attempt has this
322+ // status. RUN_STATUS_UNSPECIFIED means every status. Ignored by servers
323+ // without "ci.runs.filters".
324+ RunStatus status = 6;
325+}
326+
327+// ListRunsResponse is one page of runs.
328+message ListRunsResponse {
329+ // Runs are the runs of this page, highest id first, without jobs.
330+ repeated Run runs = 1;
331+ // NextPageToken is passed as page_token to get the next page. Empty when
332+ // there is no next page.
333+ string next_page_token = 2;
334+}
335+
336+// GetRunRequest selects a run.
337+message GetRunRequest {
338+ // Repo is the repository name.
339+ string repo = 1;
340+ // RunId is the Run.id.
341+ int64 run_id = 2;
342+ // Attempt is the attempt to read. 0 means the latest.
343+ int32 attempt = 3;
344+}
345+
346+// GetRunResponse holds a run with its jobs.
347+message GetRunResponse {
348+ // Run is the run, as of the attempt asked.
349+ Run run = 1;
350+ // Jobs are the jobs of that attempt, with their steps, in the order of the
351+ // workflow file (matrix instances in the order of the matrix). Jobs kept from
352+ // an earlier attempt by a re-run of failed jobs are included, with their own
353+ // RunJob.attempt. Empty for a run that failed on an invalid workflow file.
354+ repeated RunJob jobs = 2;
355+ // LatestAttempt is the latest attempt of the run, to offer the other ones.
356+ int32 latest_attempt = 3;
357+}
358+
359+// GetJobRequest selects a job.
360+message GetJobRequest {
361+ // Repo is the repository name.
362+ string repo = 1;
363+ // JobId is the RunJob.id.
364+ string job_id = 2;
365+ // Attempt is the RunJob.attempt to read. 0 means the latest.
366+ int32 attempt = 3;
367+}
368+
369+// GetJobResponse holds a job.
370+message GetJobResponse {
371+ // Job is the job with its steps.
372+ RunJob job = 1;
373+}
374+
375+// GetJobLogRequest selects the lines to stream.
376+message GetJobLogRequest {
377+ // Repo is the repository name.
378+ string repo = 1;
379+ // JobId is the RunJob.id.
380+ string job_id = 2;
381+ // Attempt is the RunJob.attempt whose log to read. 0 means the latest.
382+ int32 attempt = 3;
383+ // Offset is the number of the first line to send, counted from 0 across all
384+ // steps (see Step.log_start). Negative is invalid_argument.
385+ int64 offset = 4;
386+ // Follow keeps the stream open while the job runs. See GetJobLog.
387+ bool follow = 5;
388+}
389+
390+// GetJobLogResponse is one message of the log stream.
391+message GetJobLogResponse {
392+ // Offset is the number of the first line of lines.
393+ int64 offset = 1;
394+ // Lines are consecutive lines of the log, at most 1,000, at most 64 KiB of
395+ // text in total. Never empty.
396+ repeated JobLogLine lines = 2;
397+}
398+
399+// JobLogLine is one line of a job log, as the runner sent it.
400+message JobLogLine {
401+ // Step is the number of the step the line belongs to.
402+ int32 step = 1;
403+ // Time is when the runner produced the line, with nanosecond precision.
404+ google.protobuf.Timestamp time = 2;
405+ // Text is the line without its terminator, as in LogLine.text, including the
406+ // "##[group]" and "##[endgroup]" markers. Clients show it escaped.
407+ string text = 3;
408+}
409+
410+// CancelRunRequest names the run to cancel.
411+message CancelRunRequest {
412+ // Repo is the repository name.
413+ string repo = 1;
414+ // RunId is the Run.id.
415+ int64 run_id = 2;
416+}
417+
418+// CancelRunResponse holds the run after the request.
419+message CancelRunResponse {
420+ // Run is the latest attempt of the run.
421+ Run run = 1;
422+}
423+
424+// RerunRunRequest names the run to run again.
425+message RerunRunRequest {
426+ // Repo is the repository name.
427+ string repo = 1;
428+ // RunId is the Run.id.
429+ int64 run_id = 2;
430+ // FailedOnly runs again only the jobs of the latest attempt that did not
431+ // succeed (conclusion FAILURE, CANCELLED, TIMED_OUT or ACTION_REQUIRED) and
432+ // the jobs that need them, directly or not, even if they were skipped. The
433+ // other jobs keep their result, steps and log from the earlier attempt. When
434+ // false, every job runs again.
435+ bool failed_only = 3;
436+}
437+
438+// RerunRunResponse holds the new attempt.
439+message RerunRunResponse {
440+ // Run is the run as of the new attempt, QUEUED.
441+ Run run = 1;
442+}
443+
444+// ListChecksRequest names the commit.
445+message ListChecksRequest {
446+ // Repo is the repository name.
447+ string repo = 1;
448+ // Sha is the full commit id (40 lowercase hexadecimal characters for SHA-1).
449+ // Abbreviations and names are invalid_argument.
450+ string sha = 2;
451+}
452+
453+// ListChecksResponse holds the checks of a commit.
454+message ListChecksResponse {
455+ // Checks are the checks of the commit, one per job of the latest attempt of
456+ // each run, ordered by creation time then name.
457+ repeated Check checks = 1;
458+ // Truncated is true when the commit has more checks than checks holds.
459+ bool truncated = 2;
460+}
461+
462+// ListRunnersRequest selects a page of runners.
463+message ListRunnersRequest {
464+ // PageSize is the maximum number of runners to return.
465+ int32 page_size = 1;
466+ // PageToken is the next_page_token of a previous response. Empty for the
467+ // first page.
468+ string page_token = 2;
469+}
470+
471+// ListRunnersResponse is one page of runners.
472+message ListRunnersResponse {
473+ // Runners are the runners of this page, ordered by name.
474+ repeated Runner runners = 1;
475+ // NextPageToken is passed as page_token to get the next page. Empty when
476+ // there is no next page.
477+ string next_page_token = 2;
478+}
479+
480+// RemoveRunnerRequest names the runner to remove.
481+message RemoveRunnerRequest {
482+ // RunnerId is the Runner.id.
483+ string runner_id = 1;
484+}
485+
486+// RemoveRunnerResponse is empty.
487+message RemoveRunnerResponse {}
A proto/ermite/runner.proto +450 -0
@@ -0,0 +1,450 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// RunStatus is where a run, a job or a check is in its life. It is the same
10+// as GitHub's status, without the rarely used values.
11+enum RunStatus {
12+ // Not set. In a filter it means every status. Never returned by the server.
13+ RUN_STATUS_UNSPECIFIED = 0;
14+ // Waiting: for a runner, for the jobs it needs, or for its turn
15+ // (max-parallel).
16+ RUN_STATUS_QUEUED = 1;
17+ // At least one job has been given to a runner and has not ended.
18+ RUN_STATUS_IN_PROGRESS = 2;
19+ // Over. The conclusion says how it ended.
20+ RUN_STATUS_COMPLETED = 3;
21+}
22+
23+// Conclusion is how a run, a job, a step or a check ended, with GitHub's
24+// values and meaning. It is unset (CONCLUSION_UNSPECIFIED) until the status is
25+// COMPLETED.
26+enum Conclusion {
27+ // Not completed yet. Never returned by the server for a completed item.
28+ CONCLUSION_UNSPECIFIED = 0;
29+ // Everything it ran succeeded.
30+ CONCLUSION_SUCCESS = 1;
31+ // A step failed, or the job failed for another reason described in
32+ // RunJob.error (runner lost, queue timeout, log limit, feature not
33+ // supported yet, invalid workflow file).
34+ CONCLUSION_FAILURE = 2;
35+ // Cancelled by the owner, or by fail-fast, or because the runner shut down.
36+ CONCLUSION_CANCELLED = 3;
37+ // Never ran: its "if" was false, or a job it needs did not succeed, or it was
38+ // not selected. Never reported by a runner.
39+ CONCLUSION_SKIPPED = 4;
40+ // Exceeded its timeout.
41+ CONCLUSION_TIMED_OUT = 5;
42+ // Neither a success nor a failure. Defined for parity with GitHub; no
43+ // workflow job ends this way yet. For merging it counts as a success.
44+ CONCLUSION_NEUTRAL = 6;
45+ // Waits for a human action (for example the approval of an environment).
46+ // Defined for parity with GitHub; the server does not produce it before
47+ // environments exist. For merging it counts as a failure.
48+ CONCLUSION_ACTION_REQUIRED = 7;
49+}
50+
51+// StepState is where a step is in its life.
52+enum StepState {
53+ // Not set. Rejected in requests, never returned by the server.
54+ STEP_STATE_UNSPECIFIED = 0;
55+ // Known but not started.
56+ STEP_STATE_PENDING = 1;
57+ // Running.
58+ STEP_STATE_IN_PROGRESS = 2;
59+ // Over. The conclusion says how it ended. A step that did not run because
60+ // of its "if" or an earlier failure is COMPLETED with CONCLUSION_SKIPPED.
61+ STEP_STATE_COMPLETED = 3;
62+}
63+
64+// RunnerIsolation is how a runner isolates each job from the host and from
65+// other jobs. The runner chooses it (auto-detection included) and reports it
66+// at registration; the job page shows it.
67+enum RunnerIsolation {
68+ // Not set. Rejected at registration, never returned by the server.
69+ RUNNER_ISOLATION_UNSPECIFIED = 0;
70+ // One micro-VM per job, destroyed at the end.
71+ RUNNER_ISOLATION_VM = 1;
72+ // One container per job with its own Docker daemon, run by the sysbox
73+ // runtime. Shares the host kernel.
74+ RUNNER_ISOLATION_CONTAINER_SYSBOX = 2;
75+ // One privileged container per job with its own Docker daemon. Shares the
76+ // host kernel and is equivalent to root on the host: the weakest mode.
77+ RUNNER_ISOLATION_CONTAINER_PRIVILEGED = 3;
78+}
79+
80+// CancelReason says why the server asks a runner to stop a job.
81+enum CancelReason {
82+ // Not set. A WatchJobResponse with this reason is a keep-alive and carries
83+ // no request.
84+ CANCEL_REASON_UNSPECIFIED = 0;
85+ // The owner cancelled the run (RunService.CancelRun, web or CLI). The job
86+ // ends CONCLUSION_CANCELLED.
87+ CANCEL_REASON_USER = 1;
88+ // Another job of the same matrix failed and the strategy has fail-fast. The
89+ // job ends CONCLUSION_CANCELLED.
90+ CANCEL_REASON_FAIL_FAST = 2;
91+ // The job exceeded its timeout_minutes without the runner ending it. The
92+ // job ends CONCLUSION_TIMED_OUT.
93+ CANCEL_REASON_TIMEOUT = 3;
94+}
95+
96+// RunnerService is what a CI runner (ermite-runner) calls to get jobs and
97+// report on them. It is not for people: owners use RunService.
98+//
99+// Three credentials are used, each by different RPCs:
100+//
101+// - RegisterRunner: a stored token with the runner:register scope, or the
102+// root token, as "Authorization: Bearer <secret>".
103+// - FetchJob: the runner token returned by RegisterRunner, as
104+// "Authorization: Bearer <runner_token>". It identifies the runner. It
105+// only works for FetchJob.
106+// - UpdateJob, AppendLog, Heartbeat, WatchJob: the job token of the Job that
107+// FetchJob returned, as "Authorization: Bearer <job_token>". It is valid
108+// for that one job and attempt, from its assignment until it ends. The
109+// job_id of the request must be the one of the token.
110+//
111+// A request without a credential, with an unknown, expired or revoked one, or
112+// with a credential of the wrong kind for the RPC, gets unauthenticated. A
113+// job token that is valid but belongs to another job than the job_id of the
114+// request gets permission_denied. A job that has ended (concluded, cancelled,
115+// failed by the server, or whose runner was lost) no longer has a valid job
116+// token: every later call gets unauthenticated, which a runner treats as "the
117+// job is over" and stops sending anything for it. Repeated failed
118+// authentications are rate limited per client address.
119+//
120+// Runner tokens and job tokens are secrets: the server never logs them and
121+// clients must not log them. The job token stays in the runner host process; it
122+// is never given to the sandbox that runs the job's code.
123+//
124+// Servers that implement this service list "ci.runners" in
125+// GetVersionResponse.features.
126+service RunnerService {
127+ // RegisterRunner registers a runner and returns its runner token, which is
128+ // shown only in this response; the runner keeps it. The caller needs the
129+ // runner:register scope on a token that is not restricted to repositories
130+ // (a runner serves every repository), or the root token. Fails with
131+ // unauthenticated without a valid token, permission_denied without the
132+ // scope or with a repository-restricted token, invalid_argument if a field
133+ // breaks a rule, and already_exists if name is taken by another runner.
134+ rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse);
135+ // FetchJob asks for a job to run (long poll). The server holds the call
136+ // until a queued job matches the runner, or about 50 seconds (never more
137+ // than 50) pass, then answers; without a job the response has no job and the
138+ // runner calls again at once. Clients set a call timeout above 50 seconds
139+ // (60 is suggested) and must not poll when they have no free capacity.
140+ //
141+ // A job matches when every label of its runs-on is among the labels of the
142+ // runner (case-insensitive). A job is given to exactly one runner, even when
143+ // many runners and many polls compete; the assignment is recorded before
144+ // the response is sent, and the job is then in progress. If the response is
145+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
146+ // server fails it as a lost runner.
147+ //
148+ // Needs the runner token. Fails with unauthenticated when the runner was
149+ // removed (also ending a call in progress) and invalid_argument if labels
150+ // breaks a rule.
151+ rpc FetchJob(FetchJobRequest) returns (FetchJobResponse);
152+ // UpdateJob reports the state of the steps of a job and, once, how the job
153+ // ended. Needs the job token. Each call is applied whole or not at all, and
154+ // repeating an identical call changes nothing, so a runner may retry after a
155+ // network error.
156+ //
157+ // Fails with invalid_argument if a rule is broken (including the output
158+ // limits), and with failed_precondition if a step goes back (a COMPLETED
159+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
160+ // The call that carries a conclusion ends the job: its job token stops
161+ // working, the server completes the job and no further call is accepted,
162+ // so a retry of that last call after a lost response gets unauthenticated,
163+ // which means it was applied.
164+ rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse);
165+ // AppendLog appends lines to the log of a job. Needs the job token.
166+ //
167+ // The log of a job is one sequence of lines, numbered from 0 across all
168+ // steps; offset is the number of lines the server holds when the request is
169+ // applied, that is the number of the first line of the request. Calls must
170+ // go in order, one at a time, and are idempotent:
171+ //
172+ // - offset equal to the server's line count: the lines are appended;
173+ // - offset below it: the first (count - offset) lines were already stored
174+ // (a retry after a lost response) and are skipped, and the rest is
175+ // appended; a request entirely stored changes nothing;
176+ // - offset above it: out_of_range (a gap is a bug of the client; the
177+ // message gives the line count the server holds).
178+ //
179+ // The response gives the line count after the call, so a client can check
180+ // itself. A request has at most 1,000 lines whose text adds up to at most
181+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
182+ // line of more than 64 KiB is cut by the client into several lines.
183+ //
184+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
185+ // the server (text, timestamps and separators). The call that would exceed
186+ // it appends nothing; the server adds a last line saying that the limit was
187+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
188+ // answers resource_exhausted. The runner then stops the job and sends
189+ // nothing more: its calls get unauthenticated.
190+ //
191+ // Fails with invalid_argument if a rule is broken (step number, text with a
192+ // line feed or invalid UTF-8, missing time).
193+ rpc AppendLog(AppendLogRequest) returns (AppendLogResponse);
194+ // Heartbeat tells the server that the runner is alive and still working on
195+ // the job. Needs the job token. The runner calls it every 30 seconds for as
196+ // long as the job runs, from assignment to the end. Without a heartbeat for
197+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
198+ // "The self-hosted runner: <name> lost communication with the server...".
199+ // Only Heartbeat counts: other calls do not keep a job alive.
200+ rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
201+ // WatchJob streams the requests to stop a job. Needs the job token. The
202+ // runner opens it when it starts the job and keeps it open; it reopens it
203+ // after any error other than unauthenticated.
204+ //
205+ // When the stream opens and a cancellation has already been requested, the
206+ // first message is that request, so a reconnect never misses one. Only the
207+ // first reason is sent: a job already cancelled by the owner does not get a
208+ // second message when its timeout expires. The server also sends a
209+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
210+ // seconds, which the client ignores. The stream ends without error when
211+ // the job ends. The runner stops the job as the official runner does
212+ // (interrupt, then terminate, then kill) and reports the conclusion given
213+ // for the reason with UpdateJob.
214+ rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse);
215+}
216+
217+// RegisterRunnerRequest describes the runner to register.
218+message RegisterRunnerRequest {
219+ // Name identifies the runner for people (job page, logs, ListRunners) and is
220+ // unique: 1 to 64 characters among letters, digits, ".", "_" and "-". It is
221+ // not a label and never matches runs-on.
222+ string name = 1;
223+ // Labels are the labels the runner adds to its implicit ones: at most 64,
224+ // each 1 to 64 characters among letters, digits, ".", "_" and "-", for
225+ // example "ubuntu-latest". Comparison is case-insensitive and duplicates
226+ // are removed. The server adds the implicit labels "self-hosted", the os
227+ // and the arch ("linux", "x64" or "arm64"); the runner name is not a label.
228+ repeated string labels = 2;
229+ // Version is the release of the runner, for example "v0.1.0", or "devel".
230+ // Informational, at most 64 characters.
231+ string version = 3;
232+ // Isolation is how the runner isolates jobs. Required.
233+ RunnerIsolation isolation = 4;
234+ // Arch is the CPU architecture, in GitHub's names: "x64" or "arm64". Other
235+ // values are invalid_argument (the set may grow with compatible additions).
236+ string arch = 5;
237+ // Os is the operating system: "linux". Other values are invalid_argument
238+ // (the set may grow with compatible additions).
239+ string os = 6;
240+}
241+
242+// RegisterRunnerResponse holds the new runner and its secret.
243+message RegisterRunnerResponse {
244+ // RunnerId identifies the runner in ListRunners and RemoveRunner. It is not
245+ // a secret.
246+ string runner_id = 1;
247+ // RunnerToken is the secret of the runner, to present as "Authorization:
248+ // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is
249+ // returned once: the server keeps only its SHA-256, the runner must store
250+ // it (mode 0600). It stops working when the runner is removed.
251+ string runner_token = 2;
252+}
253+
254+// FetchJobRequest asks for a job.
255+message FetchJobRequest {
256+ // Labels narrows the labels offered for this call. Empty means all the
257+ // labels of the runner, which is the normal case. Otherwise each one must be
258+ // a label of the runner (invalid_argument if not) and only jobs whose runs-on
259+ // labels are all among them are offered.
260+ repeated string labels = 1;
261+}
262+
263+// FetchJobResponse holds the job given to the runner, if any.
264+message FetchJobResponse {
265+ // Job is the job to run. Unset when no job was available before the end of
266+ // the poll.
267+ Job job = 1;
268+}
269+
270+// Job is a job given to a runner: everything needed to run it, nothing more.
271+// The runner parses workflow_yaml itself; the protocol does not carry the
272+// workflow model, so it does not change when the model grows.
273+message Job {
274+ // JobId identifies the job on the server. It is opaque, unique across
275+ // repositories and runs, and the same for every attempt of the job. With
276+ // attempt it names one execution.
277+ string job_id = 1;
278+ // RunId is the run the job belongs to, the "github.run_id". It is global to
279+ // the server.
280+ int64 run_id = 2;
281+ // Attempt is the attempt of the run this job executes: 1 for the first, and
282+ // one more after each RunService.RerunRun. It is the "github.run_attempt".
283+ int32 attempt = 3;
284+ // JobToken is the secret that authenticates this job's UpdateJob,
285+ // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer
286+ // <job_token>"). It is random, valid until the job ends, survives a server
287+ // restart and is shown only in this response. It must stay in the runner
288+ // host process and never reach the job's code.
289+ string job_token = 4;
290+ // WorkflowPath is the path of the workflow file in the repository, for
291+ // example ".github/workflows/ci.yml".
292+ string workflow_path = 5;
293+ // WorkflowYaml is the content of that file at sha, as is, at most 1 MiB
294+ // (larger files fail at planning).
295+ string workflow_yaml = 6;
296+ // JobKey is the key of the job under "jobs:" in the workflow, the
297+ // "github.job".
298+ string job_key = 7;
299+ // Matrix is the JSON object of the matrix values of this instance of the
300+ // job, for example {"os":"linux","go":"1.27"}. "{}" when the job has no
301+ // matrix. It is the "matrix" context.
302+ string matrix = 8;
303+ // Contexts is a JSON object with the contexts that the server computed for
304+ // this job: "github", "needs", "strategy", "matrix", "inputs" and "vars", in
305+ // the shape GitHub gives them. The contexts "job", "runner", "steps" and
306+ // "env" belong to the runner and are not in it. Other keys may be added
307+ // later; clients ignore those they do not know.
308+ string contexts = 9;
309+ // Labels are the runs-on labels of the job, evaluated.
310+ repeated string labels = 10;
311+ // TimeoutMinutes is the timeout of the job in minutes: the value of the
312+ // workflow's timeout-minutes, or 360 when it has none. At least 1. The
313+ // runner enforces it from the start of the job; the server ends a job that
314+ // exceeds it by announcing CANCEL_REASON_TIMEOUT.
315+ int32 timeout_minutes = 11;
316+ // Repo is the name of the repository.
317+ string repo = 12;
318+ // Sha is the full id of the commit the run is for, 40 lowercase
319+ // hexadecimal characters for SHA-1. The runner reads the code at it.
320+ string sha = 13;
321+ // Ref is the full ref name that triggered the run, for example
322+ // "refs/heads/main" or "refs/tags/v1.0.0".
323+ string ref = 14;
324+}
325+
326+// UpdateJobRequest reports the steps and, in the last call, the end of a job.
327+message UpdateJobRequest {
328+ // JobId is the Job.job_id. It must be the job of the token.
329+ string job_id = 1;
330+ // Steps are the steps whose state changed or that are new. Each one is a
331+ // full snapshot of its step, applied by number. At most 1,000 steps per job
332+ // (invalid_argument beyond). Steps are numbered like GitHub's log: the
333+ // implicit "Set up job" is 1, the steps of the workflow follow in order, and
334+ // the implicit "Complete job" is last.
335+ repeated StepUpdate steps = 2;
336+ // Outputs are the outputs of the job, evaluated by the runner. Only allowed
337+ // in the call that carries the conclusion (invalid_argument otherwise).
338+ // Names are 1 to 255 characters among letters, digits, "_" and "-" and
339+ // begin with a letter or "_" and are case-insensitive: two names that
340+ // differ only by case are invalid_argument. Each value is at most 1 MiB, and
341+ // all names and values together at most 1 MiB (1,048,576 bytes); beyond that
342+ // the whole call is rejected with invalid_argument and the runner reports
343+ // the failure of the job without outputs, with a line in the log.
344+ map<string, string> outputs = 3;
345+ // Conclusion ends the job when set. A runner reports CONCLUSION_SUCCESS,
346+ // CONCLUSION_FAILURE, CONCLUSION_CANCELLED or CONCLUSION_TIMED_OUT; any
347+ // other value is invalid_argument. CONCLUSION_UNSPECIFIED means the job goes
348+ // on. Steps that were not reported COMPLETED when the job ends are
349+ // completed by the server as CONCLUSION_CANCELLED (or SKIPPED when they
350+ // never started and the job did not fail by cancellation).
351+ Conclusion conclusion = 4;
352+ // RunnerShutdown is true when the runner ends the job because it was told to
353+ // stop (service stopped, signal). Only allowed with CONCLUSION_CANCELLED.
354+ // The server then annotates the job with GitHub's message "The runner has
355+ // received a shutdown signal. This can happen when the runner service is
356+ // stopped, or a manually started runner is canceled."
357+ bool runner_shutdown = 5;
358+}
359+
360+// StepUpdate is the state of one step as the runner knows it.
361+message StepUpdate {
362+ // Number is the 1-based number of the step in the job. It is the number
363+ // used by AppendLog.
364+ int32 number = 1;
365+ // Name is the display name of the step, evaluated: the "name" of the
366+ // workflow step, or "Run <first line of the script>", as GitHub shows it.
367+ // At most 1,024 characters; the server cuts longer names.
368+ string name = 2;
369+ // State is where the step is.
370+ StepState state = 3;
371+ // Conclusion is how the step ended. Required (not UNSPECIFIED) when state is
372+ // COMPLETED, must be UNSPECIFIED otherwise. Allowed values: success,
373+ // failure, cancelled, skipped.
374+ Conclusion conclusion = 4;
375+ // StartedAt is when the step started, by the clock of the runner. Required
376+ // from IN_PROGRESS on.
377+ google.protobuf.Timestamp started_at = 5;
378+ // CompletedAt is when the step ended, by the clock of the runner. Required
379+ // when state is COMPLETED.
380+ google.protobuf.Timestamp completed_at = 6;
381+}
382+
383+// UpdateJobResponse is empty.
384+message UpdateJobResponse {}
385+
386+// AppendLogRequest carries log lines of one step.
387+message AppendLogRequest {
388+ // JobId is the Job.job_id. It must be the job of the token.
389+ string job_id = 1;
390+ // Step is the number of the step the lines belong to (see
391+ // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not
392+ // announced yet is created by the first lines.
393+ int32 step = 2;
394+ // Offset is the number of the first line of the request in the log of the
395+ // job, counted from 0 across all steps. See the rules on AppendLog.
396+ int64 offset = 3;
397+ // Lines are the lines to append, in order: at most 1,000, at most 64 KiB of
398+ // text in total.
399+ repeated LogLine lines = 4;
400+}
401+
402+// LogLine is one line of a job log.
403+message LogLine {
404+ // Time is when the line was produced, by the clock of the runner. The server
405+ // keeps it with nanosecond precision (RFC 3339) and does not reorder lines.
406+ google.protobuf.Timestamp time = 1;
407+ // Text is the line without its line terminator, valid UTF-8 (the client
408+ // replaces invalid bytes with U+FFFD). It must not contain a line feed; a
409+ // carriage return is kept. It is the line as GitHub's raw log has it,
410+ // including "##[group]" and "##[endgroup]" markers and "##[error]" lines.
411+ string text = 2;
412+}
413+
414+// AppendLogResponse tells how much of the log the server holds.
415+message AppendLogResponse {
416+ // NextOffset is the number of lines of the job log after the call, which is
417+ // the offset of the next request.
418+ int64 next_offset = 1;
419+}
420+
421+// HeartbeatRequest names the job that is alive.
422+message HeartbeatRequest {
423+ // JobId is the Job.job_id. It must be the job of the token.
424+ string job_id = 1;
425+}
426+
427+// HeartbeatResponse is empty.
428+message HeartbeatResponse {}
429+
430+// WatchJobRequest names the job to watch.
431+message WatchJobRequest {
432+ // JobId is the Job.job_id. It must be the job of the token.
433+ string job_id = 1;
434+}
435+
436+// WatchJobResponse is one message of the stream: a request to cancel the job,
437+// or a keep-alive.
438+message WatchJobResponse {
439+ // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a
440+ // keep-alive: the other fields are empty and the client ignores it. New
441+ // reasons may be added; a runner that does not know one cancels the job and
442+ // reports CONCLUSION_CANCELLED.
443+ CancelReason reason = 1;
444+ // Message is a short human-readable explanation, for the job log, for
445+ // example "Canceled by the owner". At most 1,024 characters.
446+ string message = 2;
447+ // RequestedAt is when the cancellation was requested, by the clock of the
448+ // server.
449+ google.protobuf.Timestamp requested_at = 3;
450+}
@@ -0,0 +1,450 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// RunStatus is where a run, a job or a check is in its life. It is the same
10+// as GitHub's status, without the rarely used values.
11+enum RunStatus {
12+ // Not set. In a filter it means every status. Never returned by the server.
13+ RUN_STATUS_UNSPECIFIED = 0;
14+ // Waiting: for a runner, for the jobs it needs, or for its turn
15+ // (max-parallel).
16+ RUN_STATUS_QUEUED = 1;
17+ // At least one job has been given to a runner and has not ended.
18+ RUN_STATUS_IN_PROGRESS = 2;
19+ // Over. The conclusion says how it ended.
20+ RUN_STATUS_COMPLETED = 3;
21+}
22+
23+// Conclusion is how a run, a job, a step or a check ended, with GitHub's
24+// values and meaning. It is unset (CONCLUSION_UNSPECIFIED) until the status is
25+// COMPLETED.
26+enum Conclusion {
27+ // Not completed yet. Never returned by the server for a completed item.
28+ CONCLUSION_UNSPECIFIED = 0;
29+ // Everything it ran succeeded.
30+ CONCLUSION_SUCCESS = 1;
31+ // A step failed, or the job failed for another reason described in
32+ // RunJob.error (runner lost, queue timeout, log limit, feature not
33+ // supported yet, invalid workflow file).
34+ CONCLUSION_FAILURE = 2;
35+ // Cancelled by the owner, or by fail-fast, or because the runner shut down.
36+ CONCLUSION_CANCELLED = 3;
37+ // Never ran: its "if" was false, or a job it needs did not succeed, or it was
38+ // not selected. Never reported by a runner.
39+ CONCLUSION_SKIPPED = 4;
40+ // Exceeded its timeout.
41+ CONCLUSION_TIMED_OUT = 5;
42+ // Neither a success nor a failure. Defined for parity with GitHub; no
43+ // workflow job ends this way yet. For merging it counts as a success.
44+ CONCLUSION_NEUTRAL = 6;
45+ // Waits for a human action (for example the approval of an environment).
46+ // Defined for parity with GitHub; the server does not produce it before
47+ // environments exist. For merging it counts as a failure.
48+ CONCLUSION_ACTION_REQUIRED = 7;
49+}
50+
51+// StepState is where a step is in its life.
52+enum StepState {
53+ // Not set. Rejected in requests, never returned by the server.
54+ STEP_STATE_UNSPECIFIED = 0;
55+ // Known but not started.
56+ STEP_STATE_PENDING = 1;
57+ // Running.
58+ STEP_STATE_IN_PROGRESS = 2;
59+ // Over. The conclusion says how it ended. A step that did not run because
60+ // of its "if" or an earlier failure is COMPLETED with CONCLUSION_SKIPPED.
61+ STEP_STATE_COMPLETED = 3;
62+}
63+
64+// RunnerIsolation is how a runner isolates each job from the host and from
65+// other jobs. The runner chooses it (auto-detection included) and reports it
66+// at registration; the job page shows it.
67+enum RunnerIsolation {
68+ // Not set. Rejected at registration, never returned by the server.
69+ RUNNER_ISOLATION_UNSPECIFIED = 0;
70+ // One micro-VM per job, destroyed at the end.
71+ RUNNER_ISOLATION_VM = 1;
72+ // One container per job with its own Docker daemon, run by the sysbox
73+ // runtime. Shares the host kernel.
74+ RUNNER_ISOLATION_CONTAINER_SYSBOX = 2;
75+ // One privileged container per job with its own Docker daemon. Shares the
76+ // host kernel and is equivalent to root on the host: the weakest mode.
77+ RUNNER_ISOLATION_CONTAINER_PRIVILEGED = 3;
78+}
79+
80+// CancelReason says why the server asks a runner to stop a job.
81+enum CancelReason {
82+ // Not set. A WatchJobResponse with this reason is a keep-alive and carries
83+ // no request.
84+ CANCEL_REASON_UNSPECIFIED = 0;
85+ // The owner cancelled the run (RunService.CancelRun, web or CLI). The job
86+ // ends CONCLUSION_CANCELLED.
87+ CANCEL_REASON_USER = 1;
88+ // Another job of the same matrix failed and the strategy has fail-fast. The
89+ // job ends CONCLUSION_CANCELLED.
90+ CANCEL_REASON_FAIL_FAST = 2;
91+ // The job exceeded its timeout_minutes without the runner ending it. The
92+ // job ends CONCLUSION_TIMED_OUT.
93+ CANCEL_REASON_TIMEOUT = 3;
94+}
95+
96+// RunnerService is what a CI runner (ermite-runner) calls to get jobs and
97+// report on them. It is not for people: owners use RunService.
98+//
99+// Three credentials are used, each by different RPCs:
100+//
101+// - RegisterRunner: a stored token with the runner:register scope, or the
102+// root token, as "Authorization: Bearer <secret>".
103+// - FetchJob: the runner token returned by RegisterRunner, as
104+// "Authorization: Bearer <runner_token>". It identifies the runner. It
105+// only works for FetchJob.
106+// - UpdateJob, AppendLog, Heartbeat, WatchJob: the job token of the Job that
107+// FetchJob returned, as "Authorization: Bearer <job_token>". It is valid
108+// for that one job and attempt, from its assignment until it ends. The
109+// job_id of the request must be the one of the token.
110+//
111+// A request without a credential, with an unknown, expired or revoked one, or
112+// with a credential of the wrong kind for the RPC, gets unauthenticated. A
113+// job token that is valid but belongs to another job than the job_id of the
114+// request gets permission_denied. A job that has ended (concluded, cancelled,
115+// failed by the server, or whose runner was lost) no longer has a valid job
116+// token: every later call gets unauthenticated, which a runner treats as "the
117+// job is over" and stops sending anything for it. Repeated failed
118+// authentications are rate limited per client address.
119+//
120+// Runner tokens and job tokens are secrets: the server never logs them and
121+// clients must not log them. The job token stays in the runner host process; it
122+// is never given to the sandbox that runs the job's code.
123+//
124+// Servers that implement this service list "ci.runners" in
125+// GetVersionResponse.features.
126+service RunnerService {
127+ // RegisterRunner registers a runner and returns its runner token, which is
128+ // shown only in this response; the runner keeps it. The caller needs the
129+ // runner:register scope on a token that is not restricted to repositories
130+ // (a runner serves every repository), or the root token. Fails with
131+ // unauthenticated without a valid token, permission_denied without the
132+ // scope or with a repository-restricted token, invalid_argument if a field
133+ // breaks a rule, and already_exists if name is taken by another runner.
134+ rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse);
135+ // FetchJob asks for a job to run (long poll). The server holds the call
136+ // until a queued job matches the runner, or about 50 seconds (never more
137+ // than 50) pass, then answers; without a job the response has no job and the
138+ // runner calls again at once. Clients set a call timeout above 50 seconds
139+ // (60 is suggested) and must not poll when they have no free capacity.
140+ //
141+ // A job matches when every label of its runs-on is among the labels of the
142+ // runner (case-insensitive). A job is given to exactly one runner, even when
143+ // many runners and many polls compete; the assignment is recorded before
144+ // the response is sent, and the job is then in progress. If the response is
145+ // lost the job is not offered again: with no Heartbeat for 2 minutes the
146+ // server fails it as a lost runner.
147+ //
148+ // Needs the runner token. Fails with unauthenticated when the runner was
149+ // removed (also ending a call in progress) and invalid_argument if labels
150+ // breaks a rule.
151+ rpc FetchJob(FetchJobRequest) returns (FetchJobResponse);
152+ // UpdateJob reports the state of the steps of a job and, once, how the job
153+ // ended. Needs the job token. Each call is applied whole or not at all, and
154+ // repeating an identical call changes nothing, so a runner may retry after a
155+ // network error.
156+ //
157+ // Fails with invalid_argument if a rule is broken (including the output
158+ // limits), and with failed_precondition if a step goes back (a COMPLETED
159+ // step cannot change, a step cannot go from IN_PROGRESS to PENDING).
160+ // The call that carries a conclusion ends the job: its job token stops
161+ // working, the server completes the job and no further call is accepted,
162+ // so a retry of that last call after a lost response gets unauthenticated,
163+ // which means it was applied.
164+ rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse);
165+ // AppendLog appends lines to the log of a job. Needs the job token.
166+ //
167+ // The log of a job is one sequence of lines, numbered from 0 across all
168+ // steps; offset is the number of lines the server holds when the request is
169+ // applied, that is the number of the first line of the request. Calls must
170+ // go in order, one at a time, and are idempotent:
171+ //
172+ // - offset equal to the server's line count: the lines are appended;
173+ // - offset below it: the first (count - offset) lines were already stored
174+ // (a retry after a lost response) and are skipped, and the rest is
175+ // appended; a request entirely stored changes nothing;
176+ // - offset above it: out_of_range (a gap is a bug of the client; the
177+ // message gives the line count the server holds).
178+ //
179+ // The response gives the line count after the call, so a client can check
180+ // itself. A request has at most 1,000 lines whose text adds up to at most
181+ // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A
182+ // line of more than 64 KiB is cut by the client into several lines.
183+ //
184+ // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by
185+ // the server (text, timestamps and separators). The call that would exceed
186+ // it appends nothing; the server adds a last line saying that the limit was
187+ // reached, ends the job with CONCLUSION_FAILURE and RunJob.error set, and
188+ // answers resource_exhausted. The runner then stops the job and sends
189+ // nothing more: its calls get unauthenticated.
190+ //
191+ // Fails with invalid_argument if a rule is broken (step number, text with a
192+ // line feed or invalid UTF-8, missing time).
193+ rpc AppendLog(AppendLogRequest) returns (AppendLogResponse);
194+ // Heartbeat tells the server that the runner is alive and still working on
195+ // the job. Needs the job token. The runner calls it every 30 seconds for as
196+ // long as the job runs, from assignment to the end. Without a heartbeat for
197+ // 2 minutes the server ends the job with CONCLUSION_FAILURE and the message
198+ // "The self-hosted runner: <name> lost communication with the server...".
199+ // Only Heartbeat counts: other calls do not keep a job alive.
200+ rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
201+ // WatchJob streams the requests to stop a job. Needs the job token. The
202+ // runner opens it when it starts the job and keeps it open; it reopens it
203+ // after any error other than unauthenticated.
204+ //
205+ // When the stream opens and a cancellation has already been requested, the
206+ // first message is that request, so a reconnect never misses one. Only the
207+ // first reason is sent: a job already cancelled by the owner does not get a
208+ // second message when its timeout expires. The server also sends a
209+ // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30
210+ // seconds, which the client ignores. The stream ends without error when
211+ // the job ends. The runner stops the job as the official runner does
212+ // (interrupt, then terminate, then kill) and reports the conclusion given
213+ // for the reason with UpdateJob.
214+ rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse);
215+}
216+
217+// RegisterRunnerRequest describes the runner to register.
218+message RegisterRunnerRequest {
219+ // Name identifies the runner for people (job page, logs, ListRunners) and is
220+ // unique: 1 to 64 characters among letters, digits, ".", "_" and "-". It is
221+ // not a label and never matches runs-on.
222+ string name = 1;
223+ // Labels are the labels the runner adds to its implicit ones: at most 64,
224+ // each 1 to 64 characters among letters, digits, ".", "_" and "-", for
225+ // example "ubuntu-latest". Comparison is case-insensitive and duplicates
226+ // are removed. The server adds the implicit labels "self-hosted", the os
227+ // and the arch ("linux", "x64" or "arm64"); the runner name is not a label.
228+ repeated string labels = 2;
229+ // Version is the release of the runner, for example "v0.1.0", or "devel".
230+ // Informational, at most 64 characters.
231+ string version = 3;
232+ // Isolation is how the runner isolates jobs. Required.
233+ RunnerIsolation isolation = 4;
234+ // Arch is the CPU architecture, in GitHub's names: "x64" or "arm64". Other
235+ // values are invalid_argument (the set may grow with compatible additions).
236+ string arch = 5;
237+ // Os is the operating system: "linux". Other values are invalid_argument
238+ // (the set may grow with compatible additions).
239+ string os = 6;
240+}
241+
242+// RegisterRunnerResponse holds the new runner and its secret.
243+message RegisterRunnerResponse {
244+ // RunnerId identifies the runner in ListRunners and RemoveRunner. It is not
245+ // a secret.
246+ string runner_id = 1;
247+ // RunnerToken is the secret of the runner, to present as "Authorization:
248+ // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is
249+ // returned once: the server keeps only its SHA-256, the runner must store
250+ // it (mode 0600). It stops working when the runner is removed.
251+ string runner_token = 2;
252+}
253+
254+// FetchJobRequest asks for a job.
255+message FetchJobRequest {
256+ // Labels narrows the labels offered for this call. Empty means all the
257+ // labels of the runner, which is the normal case. Otherwise each one must be
258+ // a label of the runner (invalid_argument if not) and only jobs whose runs-on
259+ // labels are all among them are offered.
260+ repeated string labels = 1;
261+}
262+
263+// FetchJobResponse holds the job given to the runner, if any.
264+message FetchJobResponse {
265+ // Job is the job to run. Unset when no job was available before the end of
266+ // the poll.
267+ Job job = 1;
268+}
269+
270+// Job is a job given to a runner: everything needed to run it, nothing more.
271+// The runner parses workflow_yaml itself; the protocol does not carry the
272+// workflow model, so it does not change when the model grows.
273+message Job {
274+ // JobId identifies the job on the server. It is opaque, unique across
275+ // repositories and runs, and the same for every attempt of the job. With
276+ // attempt it names one execution.
277+ string job_id = 1;
278+ // RunId is the run the job belongs to, the "github.run_id". It is global to
279+ // the server.
280+ int64 run_id = 2;
281+ // Attempt is the attempt of the run this job executes: 1 for the first, and
282+ // one more after each RunService.RerunRun. It is the "github.run_attempt".
283+ int32 attempt = 3;
284+ // JobToken is the secret that authenticates this job's UpdateJob,
285+ // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer
286+ // <job_token>"). It is random, valid until the job ends, survives a server
287+ // restart and is shown only in this response. It must stay in the runner
288+ // host process and never reach the job's code.
289+ string job_token = 4;
290+ // WorkflowPath is the path of the workflow file in the repository, for
291+ // example ".github/workflows/ci.yml".
292+ string workflow_path = 5;
293+ // WorkflowYaml is the content of that file at sha, as is, at most 1 MiB
294+ // (larger files fail at planning).
295+ string workflow_yaml = 6;
296+ // JobKey is the key of the job under "jobs:" in the workflow, the
297+ // "github.job".
298+ string job_key = 7;
299+ // Matrix is the JSON object of the matrix values of this instance of the
300+ // job, for example {"os":"linux","go":"1.27"}. "{}" when the job has no
301+ // matrix. It is the "matrix" context.
302+ string matrix = 8;
303+ // Contexts is a JSON object with the contexts that the server computed for
304+ // this job: "github", "needs", "strategy", "matrix", "inputs" and "vars", in
305+ // the shape GitHub gives them. The contexts "job", "runner", "steps" and
306+ // "env" belong to the runner and are not in it. Other keys may be added
307+ // later; clients ignore those they do not know.
308+ string contexts = 9;
309+ // Labels are the runs-on labels of the job, evaluated.
310+ repeated string labels = 10;
311+ // TimeoutMinutes is the timeout of the job in minutes: the value of the
312+ // workflow's timeout-minutes, or 360 when it has none. At least 1. The
313+ // runner enforces it from the start of the job; the server ends a job that
314+ // exceeds it by announcing CANCEL_REASON_TIMEOUT.
315+ int32 timeout_minutes = 11;
316+ // Repo is the name of the repository.
317+ string repo = 12;
318+ // Sha is the full id of the commit the run is for, 40 lowercase
319+ // hexadecimal characters for SHA-1. The runner reads the code at it.
320+ string sha = 13;
321+ // Ref is the full ref name that triggered the run, for example
322+ // "refs/heads/main" or "refs/tags/v1.0.0".
323+ string ref = 14;
324+}
325+
326+// UpdateJobRequest reports the steps and, in the last call, the end of a job.
327+message UpdateJobRequest {
328+ // JobId is the Job.job_id. It must be the job of the token.
329+ string job_id = 1;
330+ // Steps are the steps whose state changed or that are new. Each one is a
331+ // full snapshot of its step, applied by number. At most 1,000 steps per job
332+ // (invalid_argument beyond). Steps are numbered like GitHub's log: the
333+ // implicit "Set up job" is 1, the steps of the workflow follow in order, and
334+ // the implicit "Complete job" is last.
335+ repeated StepUpdate steps = 2;
336+ // Outputs are the outputs of the job, evaluated by the runner. Only allowed
337+ // in the call that carries the conclusion (invalid_argument otherwise).
338+ // Names are 1 to 255 characters among letters, digits, "_" and "-" and
339+ // begin with a letter or "_" and are case-insensitive: two names that
340+ // differ only by case are invalid_argument. Each value is at most 1 MiB, and
341+ // all names and values together at most 1 MiB (1,048,576 bytes); beyond that
342+ // the whole call is rejected with invalid_argument and the runner reports
343+ // the failure of the job without outputs, with a line in the log.
344+ map<string, string> outputs = 3;
345+ // Conclusion ends the job when set. A runner reports CONCLUSION_SUCCESS,
346+ // CONCLUSION_FAILURE, CONCLUSION_CANCELLED or CONCLUSION_TIMED_OUT; any
347+ // other value is invalid_argument. CONCLUSION_UNSPECIFIED means the job goes
348+ // on. Steps that were not reported COMPLETED when the job ends are
349+ // completed by the server as CONCLUSION_CANCELLED (or SKIPPED when they
350+ // never started and the job did not fail by cancellation).
351+ Conclusion conclusion = 4;
352+ // RunnerShutdown is true when the runner ends the job because it was told to
353+ // stop (service stopped, signal). Only allowed with CONCLUSION_CANCELLED.
354+ // The server then annotates the job with GitHub's message "The runner has
355+ // received a shutdown signal. This can happen when the runner service is
356+ // stopped, or a manually started runner is canceled."
357+ bool runner_shutdown = 5;
358+}
359+
360+// StepUpdate is the state of one step as the runner knows it.
361+message StepUpdate {
362+ // Number is the 1-based number of the step in the job. It is the number
363+ // used by AppendLog.
364+ int32 number = 1;
365+ // Name is the display name of the step, evaluated: the "name" of the
366+ // workflow step, or "Run <first line of the script>", as GitHub shows it.
367+ // At most 1,024 characters; the server cuts longer names.
368+ string name = 2;
369+ // State is where the step is.
370+ StepState state = 3;
371+ // Conclusion is how the step ended. Required (not UNSPECIFIED) when state is
372+ // COMPLETED, must be UNSPECIFIED otherwise. Allowed values: success,
373+ // failure, cancelled, skipped.
374+ Conclusion conclusion = 4;
375+ // StartedAt is when the step started, by the clock of the runner. Required
376+ // from IN_PROGRESS on.
377+ google.protobuf.Timestamp started_at = 5;
378+ // CompletedAt is when the step ended, by the clock of the runner. Required
379+ // when state is COMPLETED.
380+ google.protobuf.Timestamp completed_at = 6;
381+}
382+
383+// UpdateJobResponse is empty.
384+message UpdateJobResponse {}
385+
386+// AppendLogRequest carries log lines of one step.
387+message AppendLogRequest {
388+ // JobId is the Job.job_id. It must be the job of the token.
389+ string job_id = 1;
390+ // Step is the number of the step the lines belong to (see
391+ // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not
392+ // announced yet is created by the first lines.
393+ int32 step = 2;
394+ // Offset is the number of the first line of the request in the log of the
395+ // job, counted from 0 across all steps. See the rules on AppendLog.
396+ int64 offset = 3;
397+ // Lines are the lines to append, in order: at most 1,000, at most 64 KiB of
398+ // text in total.
399+ repeated LogLine lines = 4;
400+}
401+
402+// LogLine is one line of a job log.
403+message LogLine {
404+ // Time is when the line was produced, by the clock of the runner. The server
405+ // keeps it with nanosecond precision (RFC 3339) and does not reorder lines.
406+ google.protobuf.Timestamp time = 1;
407+ // Text is the line without its line terminator, valid UTF-8 (the client
408+ // replaces invalid bytes with U+FFFD). It must not contain a line feed; a
409+ // carriage return is kept. It is the line as GitHub's raw log has it,
410+ // including "##[group]" and "##[endgroup]" markers and "##[error]" lines.
411+ string text = 2;
412+}
413+
414+// AppendLogResponse tells how much of the log the server holds.
415+message AppendLogResponse {
416+ // NextOffset is the number of lines of the job log after the call, which is
417+ // the offset of the next request.
418+ int64 next_offset = 1;
419+}
420+
421+// HeartbeatRequest names the job that is alive.
422+message HeartbeatRequest {
423+ // JobId is the Job.job_id. It must be the job of the token.
424+ string job_id = 1;
425+}
426+
427+// HeartbeatResponse is empty.
428+message HeartbeatResponse {}
429+
430+// WatchJobRequest names the job to watch.
431+message WatchJobRequest {
432+ // JobId is the Job.job_id. It must be the job of the token.
433+ string job_id = 1;
434+}
435+
436+// WatchJobResponse is one message of the stream: a request to cancel the job,
437+// or a keep-alive.
438+message WatchJobResponse {
439+ // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a
440+ // keep-alive: the other fields are empty and the client ignores it. New
441+ // reasons may be added; a runner that does not know one cancels the job and
442+ // reports CONCLUSION_CANCELLED.
443+ CancelReason reason = 1;
444+ // Message is a short human-readable explanation, for the job log, for
445+ // example "Canceled by the owner". At most 1,024 characters.
446+ string message = 2;
447+ // RequestedAt is when the cancellation was requested, by the clock of the
448+ // server.
449+ google.protobuf.Timestamp requested_at = 3;
450+}
M proto/ermite/server.proto +5 -1
@@ -31,7 +31,11 @@ message GetVersionResponse {
3131 // that this server implements, for example "repo.update" or
3232 // "events.resume". A client that needs a capability missing from this list
3333 // should warn instead of failing on an unimplemented RPC or ignored field.
34- // Identifiers are lowercase, dot-separated and never reused.
34+ // Identifiers are lowercase, dot-separated and never reused. The CI
35+ // identifiers are "ci.runs" (RunService runs, jobs, logs, checks, and the
36+ // run_updated and job_updated events), "ci.runs.filters" (ListRuns honours
37+ // its filters) and "ci.runners" (RunnerService, and RunService.ListRunners
38+ // and RemoveRunner).
3539 repeated string features = 2;
3640 }
3741
@@ -31,7 +31,11 @@ message GetVersionResponse {
31 // that this server implements, for example "repo.update" or31 // that this server implements, for example "repo.update" or
32 // "events.resume". A client that needs a capability missing from this list32 // "events.resume". A client that needs a capability missing from this list
33 // should warn instead of failing on an unimplemented RPC or ignored field.33 // should warn instead of failing on an unimplemented RPC or ignored field.
34- // Identifiers are lowercase, dot-separated and never reused.34+ // Identifiers are lowercase, dot-separated and never reused. The CI
35+ // identifiers are "ci.runs" (RunService runs, jobs, logs, checks, and the
36+ // run_updated and job_updated events), "ci.runs.filters" (ListRuns honours
37+ // its filters) and "ci.runners" (RunnerService, and RunService.ListRunners
38+ // and RemoveRunner).
35 repeated string features = 2;39 repeated string features = 2;
36 }40 }
37 41

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