feat(api): add CompleteJob, JobEnded, poll ids and versions to the CI protocol
Review fixes for the runner protocol: - CompleteJob ends a job and is idempotent (stored result, applied flag); UpdateJob no longer carries the conclusion or the outputs. - After the end of a job, calls answer failed_precondition with a JobEnded detail; unauthenticated means a bad credential only. The job token is recognised for 24 hours after the end. - Server-side enforcement of cancellation (60 s) and timeouts (assignment clock plus slack). - FetchJob poll_id, retry_after, acknowledgement by the first Heartbeat, heartbeat deadline from max(last heartbeat, server start). - WatchJob messages are a oneof (keepalive, cancel, ended). - Run.version and RunJob.version replace the event ordering promise. - AppendLog step ordering rule, LogLine.continued for long lines, log limit is failed_precondition. - Token formats, runner:register trust note, RegisterRunner replace.
- commit
- abba0da6a75834ae23e6676263945d1eb72e51a4
- author
- brice · · 2026-10-12 14:16 UTC
- parent
- 0e0b3ac
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +1297 -370
M README.md +1 -1
| @@ -22,7 +22,7 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes. | |||
| 22 | 22 | | `MergeRequestService` | `CreateMergeRequest`, `ListMergeRequests`, `GetMergeRequest` (commits, diff, comments), `AddComment`, `ResolveComment`, `CloseMergeRequest`, `MergeMergeRequest`, `MarkRead` | | |
| 23 | 23 | | `SessionService` | `CreateLoginLink`, `SignOutEverywhere` (owner only) | | |
| 24 | 24 | | `EventService` | `Watch` (streamed, resumable) | | |
| 25 | - | | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed); for CI runners | | |
| 25 | + | | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed), `CompleteJob` (idempotent); for CI runners | | |
| 26 | 26 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `CancelRun`, `RerunRun`, `ListChecks`, `ListRunners`, `RemoveRunner` | | |
| 27 | 27 | | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) | | |
| 28 | 28 | ||
| @@ -22,7 +22,7 @@ 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 | | 25 | + | | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed), `CompleteJob` (idempotent); for CI runners | |
| 26 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `CancelRun`, `RerunRun`, `ListChecks`, `ListRunners`, `RemoveRunner` | | 26 | | `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `CancelRun`, `RerunRun`, `ListChecks`, `ListRunners`, `RemoveRunner` | | ||
| 27 | | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) | | 27 | | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) | | ||
| 28 | 28 | ||||
M gen/ermite/ermiteconnect/event.connect.go +8 -4
| @@ -56,8 +56,10 @@ type EventServiceClient interface { | |||
| 56 | 56 | // "home"), subject to the same visibility. They carry the state after the | |
| 57 | 57 | // change and are published when a run or a job is created, changes status | |
| 58 | 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 | |
| 59 | + | // line: follow a log with RunService.GetJobLog). The order of these events | |
| 60 | + | // is not guaranteed, between jobs and runs or between two updates of the same | |
| 61 | + | // one: a client keeps the copy with the higher Run.version or | |
| 62 | + | // RunJob.version and drops the other. There is no event for a change of title or | |
| 61 | 63 | // description, nor for a resolved comment: after a gap, or to see such | |
| 62 | 64 | // changes, clients read the merge request again. A merge publishes | |
| 63 | 65 | // merge_request_merged, then pushed for the target branch. | |
| @@ -126,8 +128,10 @@ type EventServiceHandler interface { | |||
| 126 | 128 | // "home"), subject to the same visibility. They carry the state after the | |
| 127 | 129 | // change and are published when a run or a job is created, changes status | |
| 128 | 130 | // 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 | |
| 131 | + | // line: follow a log with RunService.GetJobLog). The order of these events | |
| 132 | + | // is not guaranteed, between jobs and runs or between two updates of the same | |
| 133 | + | // one: a client keeps the copy with the higher Run.version or | |
| 134 | + | // RunJob.version and drops the other. There is no event for a change of title or | |
| 131 | 135 | // description, nor for a resolved comment: after a gap, or to see such | |
| 132 | 136 | // changes, clients read the merge request again. A merge publishes | |
| 133 | 137 | // merge_request_merged, then pushed for the target branch. | |
| @@ -56,8 +56,10 @@ type EventServiceClient interface { | |||||
| 56 | // "home"), subject to the same visibility. They carry the state after the | 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 | 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 | 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 | 59 | + | // line: follow a log with RunService.GetJobLog). The order of these events |
| 60 | - | // before the update of its run. There is no event for a change of title or | 60 | + | // is not guaranteed, between jobs and runs or between two updates of the same |
| 61 | + | // one: a client keeps the copy with the higher Run.version or | |||
| 62 | + | // RunJob.version and drops the other. There is no event for a change of title or | |||
| 61 | // description, nor for a resolved comment: after a gap, or to see such | 63 | // description, nor for a resolved comment: after a gap, or to see such | ||
| 62 | // changes, clients read the merge request again. A merge publishes | 64 | // changes, clients read the merge request again. A merge publishes | ||
| 63 | // merge_request_merged, then pushed for the target branch. | 65 | // merge_request_merged, then pushed for the target branch. | ||
| @@ -126,8 +128,10 @@ type EventServiceHandler interface { | |||||
| 126 | // "home"), subject to the same visibility. They carry the state after the | 128 | // "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 | 129 | // 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 | 130 | // 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 | 131 | + | // line: follow a log with RunService.GetJobLog). The order of these events |
| 130 | - | // before the update of its run. There is no event for a change of title or | 132 | + | // is not guaranteed, between jobs and runs or between two updates of the same |
| 133 | + | // one: a client keeps the copy with the higher Run.version or | |||
| 134 | + | // RunJob.version and drops the other. There is no event for a change of title or | |||
| 131 | // description, nor for a resolved comment: after a gap, or to see such | 135 | // description, nor for a resolved comment: after a gap, or to see such | ||
| 132 | // changes, clients read the merge request again. A merge publishes | 136 | // changes, clients read the merge request again. A merge publishes | ||
| 133 | // merge_request_merged, then pushed for the target branch. | 137 | // merge_request_merged, then pushed for the target branch. | ||
M gen/ermite/ermiteconnect/runner.connect.go +247 -74
| @@ -48,6 +48,9 @@ const ( | |||
| 48 | 48 | RunnerServiceHeartbeatProcedure = "/ermite.RunnerService/Heartbeat" | |
| 49 | 49 | // RunnerServiceWatchJobProcedure is the fully-qualified name of the RunnerService's WatchJob RPC. | |
| 50 | 50 | RunnerServiceWatchJobProcedure = "/ermite.RunnerService/WatchJob" | |
| 51 | + | // RunnerServiceCompleteJobProcedure is the fully-qualified name of the RunnerService's CompleteJob | |
| 52 | + | // RPC. | |
| 53 | + | RunnerServiceCompleteJobProcedure = "/ermite.RunnerService/CompleteJob" | |
| 51 | 54 | ) | |
| 52 | 55 | ||
| 53 | 56 | // RunnerServiceClient is a client for the ermite.RunnerService service. | |
| @@ -58,37 +61,54 @@ type RunnerServiceClient interface { | |||
| 58 | 61 | // (a runner serves every repository), or the root token. Fails with | |
| 59 | 62 | // unauthenticated without a valid token, permission_denied without the | |
| 60 | 63 | // 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. | |
| 64 | + | // breaks a rule, and already_exists if name is taken by another runner and | |
| 65 | + | // replace is not set. | |
| 62 | 66 | RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) | |
| 63 | 67 | // FetchJob asks for a job to run (long poll). The server holds the call | |
| 64 | 68 | // until a queued job matches the runner, or about 50 seconds (never more | |
| 65 | 69 | // 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. | |
| 70 | + | // runner calls again after retry_after. Clients set a call timeout above 50 | |
| 71 | + | // seconds (60 is suggested) and must not poll when they have no free | |
| 72 | + | // capacity. | |
| 68 | 73 | // | |
| 69 | 74 | // A job matches when every label of its runs-on is among the labels of the | |
| 70 | 75 | // runner (case-insensitive). A job is given to exactly one runner, even when | |
| 71 | 76 | // 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. | |
| 77 | + | // the response is sent. The job stays QUEUED for the owner until it is | |
| 78 | + | // acknowledged. | |
| 79 | + | // | |
| 80 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |
| 81 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |
| 82 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |
| 83 | + | // response was lost, or the runner died), the server cancels the assignment: | |
| 84 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |
| 85 | + | // and can be given to any runner, the same one included; nothing the old | |
| 86 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |
| 87 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |
| 88 | + | // and an error saying so. | |
| 89 | + | // | |
| 90 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |
| 91 | + | // a call with the same poll_id as an earlier call of the same runner that | |
| 92 | + | // assigned a job returns that same assignment (same Job, same token) as long | |
| 93 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |
| 94 | + | // | |
| 95 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |
| 96 | + | // not keep the job token, so the job fails with the lost-runner error after | |
| 97 | + | // the heartbeat timeout (see Heartbeat). | |
| 75 | 98 | // | |
| 76 | 99 | // Needs the runner token. Fails with unauthenticated when the runner was | |
| 77 | 100 | // removed (also ending a call in progress) and invalid_argument if labels | |
| 78 | - | // breaks a rule. | |
| 101 | + | // or poll_id break a rule. | |
| 79 | 102 | 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. | |
| 103 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the | |
| 104 | + | // job token. Each call is applied whole or not at all, and repeating an | |
| 105 | + | // identical call changes nothing, so a runner may retry after a network | |
| 106 | + | // error. To end the job use CompleteJob. | |
| 84 | 107 | // | |
| 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. | |
| 108 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition | |
| 109 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from | |
| 110 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail | |
| 111 | + | // if the job has ended. | |
| 92 | 112 | UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) | |
| 93 | 113 | // AppendLog appends lines to the log of a job. Needs the job token. | |
| 94 | 114 | // | |
| @@ -100,46 +120,102 @@ type RunnerServiceClient interface { | |||
| 100 | 120 | // - offset equal to the server's line count: the lines are appended; | |
| 101 | 121 | // - offset below it: the first (count - offset) lines were already stored | |
| 102 | 122 | // (a retry after a lost response) and are skipped, and the rest is | |
| 103 | - | // appended; a request entirely stored changes nothing; | |
| 123 | + | // appended; a request entirely stored changes nothing. The skipped lines | |
| 124 | + | // are not compared with the stored ones: a retry whose lines differ from | |
| 125 | + | // the first attempt is silently dropped, so a client must resend the | |
| 126 | + | // same lines at the same offset; | |
| 104 | 127 | // - offset above it: out_of_range (a gap is a bug of the client; the | |
| 105 | 128 | // message gives the line count the server holds). | |
| 106 | 129 | // | |
| 107 | 130 | // 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. | |
| 131 | + | // itself. Offsets resume only after a network error within the same runner | |
| 132 | + | // process; they are not a way to resume a job after a crash. | |
| 133 | + | // | |
| 134 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |
| 135 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |
| 136 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |
| 137 | + | // | |
| 138 | + | // Steps. The step of a request must be at least the step of the last line | |
| 139 | + | // the server holds, because the log is ordered by step (applied lines only: | |
| 140 | + | // lines skipped as already stored are not checked); a lower step is | |
| 141 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |
| 142 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |
| 143 | + | // empty name until an UpdateJob gives one. | |
| 111 | 144 | // | |
| 112 | 145 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | |
| 113 | 146 | // the server (text, timestamps and separators). The call that would exceed | |
| 114 | 147 | // it appends nothing; the server adds a last line saying that the limit was | |
| 115 | 148 | // reached, ends the job with CI_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. | |
| 149 | + | // answers failed_precondition with a JobEnded detail like any call after the | |
| 150 | + | // end of a job. The runner then stops the job. | |
| 151 | + | // | |
| 152 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |
| 153 | + | // its AppendLog lines before it calls CompleteJob. | |
| 118 | 154 | // | |
| 119 | 155 | // Fails with invalid_argument if a rule is broken (step number, text with a | |
| 120 | - | // line feed or invalid UTF-8, missing time). | |
| 156 | + | // line feed or invalid UTF-8, missing time, a continued first line). | |
| 121 | 157 | AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) | |
| 122 | 158 | // 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 CI_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. | |
| 159 | + | // the job. Needs the job token. The first one acknowledges the assignment | |
| 160 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as | |
| 161 | + | // the job runs. The deadline is 2 minutes after the later of the last | |
| 162 | + | // heartbeat and the start of the server (so a server restart does not fail | |
| 163 | + | // the jobs of runners that could not reach it): past it the server ends the | |
| 164 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |
| 165 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |
| 166 | + | // other calls do not keep a job alive. | |
| 167 | + | // | |
| 168 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |
| 169 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |
| 170 | + | // second tick, and keeps the job running meanwhile. | |
| 128 | 171 | 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 | |
| 172 | + | // WatchJob streams what the runner must react to. Needs the job token. The | |
| 130 | 173 | // runner opens it when it starts the job and keeps it open; it reopens it | |
| 131 | - | // after any error other than unauthenticated. | |
| 174 | + | // after any error other than unauthenticated and the failed_precondition | |
| 175 | + | // that says the job ended, and after a clean close of | |
| 176 | + | // the stream that did not end with a JobEnded message. | |
| 132 | 177 | // | |
| 133 | 178 | // When the stream opens and a cancellation has already been requested, the | |
| 134 | 179 | // first message is that request, so a reconnect never misses one. Only the | |
| 135 | 180 | // 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 | |
| 181 | + | // second message when its time budget expires. The server also sends a | |
| 182 | + | // Keepalive at least every 30 seconds, which the client ignores. When the | |
| 183 | + | // job ends the server sends JobEnded, then closes the stream without error. | |
| 184 | + | // On a job that has already ended the call fails like the other RPCs | |
| 185 | + | // (failed_precondition with a JobEnded detail). | |
| 186 | + | // | |
| 187 | + | // On a CancelRequest the runner stops the job as the official runner does | |
| 140 | 188 | // (interrupt, then terminate, then kill) and reports the conclusion given | |
| 141 | - | // for the reason with UpdateJob. | |
| 189 | + | // for the reason with CompleteJob. | |
| 190 | + | // | |
| 191 | + | // The server does not trust the runner to obey. If no conclusion arrives | |
| 192 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |
| 193 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |
| 194 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |
| 195 | + | // saying that the runner did not answer, and the steps not completed are | |
| 196 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |
| 142 | 197 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | |
| 198 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |
| 199 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |
| 200 | + | // hours after the end of the job, however it ended. The call is applied whole | |
| 201 | + | // or not at all. Once the job has ended, the token works for no other RPC | |
| 202 | + | // (they answer failed_precondition with a JobEnded detail). | |
| 203 | + | // | |
| 204 | + | // It is idempotent. The first call that finds the job running ends it and | |
| 205 | + | // answers applied = true. Any later call, a retry after a lost response or a | |
| 206 | + | // call after the server ended the job itself, is not validated and changes | |
| 207 | + | // nothing: it answers OK with the stored conclusion and error and | |
| 208 | + | // applied = false (a runner whose conclusion differs from the stored one | |
| 209 | + | // simply logs it; the outputs it sent are dropped). | |
| 210 | + | // | |
| 211 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |
| 212 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |
| 213 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |
| 214 | + | // they never started and the job did not end by cancellation). | |
| 215 | + | // | |
| 216 | + | // Fails with invalid_argument if a rule is broken (including the output | |
| 217 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |
| 218 | + | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | |
| 143 | 219 | } | |
| 144 | 220 | ||
| 145 | 221 | // NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it | |
| @@ -189,6 +265,12 @@ func NewRunnerServiceClient(httpClient connect.HTTPClient, baseURL string, opts | |||
| 189 | 265 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | |
| 190 | 266 | connect.WithClientOptions(opts...), | |
| 191 | 267 | ), | |
| 268 | + | completeJob: connect.NewClient[ermite.CompleteJobRequest, ermite.CompleteJobResponse]( | |
| 269 | + | httpClient, | |
| 270 | + | baseURL+RunnerServiceCompleteJobProcedure, | |
| 271 | + | connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")), | |
| 272 | + | connect.WithClientOptions(opts...), | |
| 273 | + | ), | |
| 192 | 274 | } | |
| 193 | 275 | } | |
| 194 | 276 | ||
| @@ -200,6 +282,7 @@ type runnerServiceClient struct { | |||
| 200 | 282 | appendLog *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse] | |
| 201 | 283 | heartbeat *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse] | |
| 202 | 284 | watchJob *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse] | |
| 285 | + | completeJob *connect.Client[ermite.CompleteJobRequest, ermite.CompleteJobResponse] | |
| 203 | 286 | } | |
| 204 | 287 | ||
| 205 | 288 | // RegisterRunner calls ermite.RunnerService.RegisterRunner. | |
| @@ -232,6 +315,11 @@ func (c *runnerServiceClient) WatchJob(ctx context.Context, req *connect.Request | |||
| 232 | 315 | return c.watchJob.CallServerStream(ctx, req) | |
| 233 | 316 | } | |
| 234 | 317 | ||
| 318 | + | // CompleteJob calls ermite.RunnerService.CompleteJob. | |
| 319 | + | func (c *runnerServiceClient) CompleteJob(ctx context.Context, req *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | |
| 320 | + | return c.completeJob.CallUnary(ctx, req) | |
| 321 | + | } | |
| 322 | + | ||
| 235 | 323 | // RunnerServiceHandler is an implementation of the ermite.RunnerService service. | |
| 236 | 324 | type RunnerServiceHandler interface { | |
| 237 | 325 | // RegisterRunner registers a runner and returns its runner token, which is | |
| @@ -240,37 +328,54 @@ type RunnerServiceHandler interface { | |||
| 240 | 328 | // (a runner serves every repository), or the root token. Fails with | |
| 241 | 329 | // unauthenticated without a valid token, permission_denied without the | |
| 242 | 330 | // 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. | |
| 331 | + | // breaks a rule, and already_exists if name is taken by another runner and | |
| 332 | + | // replace is not set. | |
| 244 | 333 | RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) | |
| 245 | 334 | // FetchJob asks for a job to run (long poll). The server holds the call | |
| 246 | 335 | // until a queued job matches the runner, or about 50 seconds (never more | |
| 247 | 336 | // 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. | |
| 337 | + | // runner calls again after retry_after. Clients set a call timeout above 50 | |
| 338 | + | // seconds (60 is suggested) and must not poll when they have no free | |
| 339 | + | // capacity. | |
| 250 | 340 | // | |
| 251 | 341 | // A job matches when every label of its runs-on is among the labels of the | |
| 252 | 342 | // runner (case-insensitive). A job is given to exactly one runner, even when | |
| 253 | 343 | // 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. | |
| 344 | + | // the response is sent. The job stays QUEUED for the owner until it is | |
| 345 | + | // acknowledged. | |
| 346 | + | // | |
| 347 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |
| 348 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |
| 349 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |
| 350 | + | // response was lost, or the runner died), the server cancels the assignment: | |
| 351 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |
| 352 | + | // and can be given to any runner, the same one included; nothing the old | |
| 353 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |
| 354 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |
| 355 | + | // and an error saying so. | |
| 356 | + | // | |
| 357 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |
| 358 | + | // a call with the same poll_id as an earlier call of the same runner that | |
| 359 | + | // assigned a job returns that same assignment (same Job, same token) as long | |
| 360 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |
| 361 | + | // | |
| 362 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |
| 363 | + | // not keep the job token, so the job fails with the lost-runner error after | |
| 364 | + | // the heartbeat timeout (see Heartbeat). | |
| 257 | 365 | // | |
| 258 | 366 | // Needs the runner token. Fails with unauthenticated when the runner was | |
| 259 | 367 | // removed (also ending a call in progress) and invalid_argument if labels | |
| 260 | - | // breaks a rule. | |
| 368 | + | // or poll_id break a rule. | |
| 261 | 369 | 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. | |
| 370 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the | |
| 371 | + | // job token. Each call is applied whole or not at all, and repeating an | |
| 372 | + | // identical call changes nothing, so a runner may retry after a network | |
| 373 | + | // error. To end the job use CompleteJob. | |
| 266 | 374 | // | |
| 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. | |
| 375 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition | |
| 376 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from | |
| 377 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail | |
| 378 | + | // if the job has ended. | |
| 274 | 379 | UpdateJob(context.Context, *connect.Request[ermite.UpdateJobRequest]) (*connect.Response[ermite.UpdateJobResponse], error) | |
| 275 | 380 | // AppendLog appends lines to the log of a job. Needs the job token. | |
| 276 | 381 | // | |
| @@ -282,46 +387,102 @@ type RunnerServiceHandler interface { | |||
| 282 | 387 | // - offset equal to the server's line count: the lines are appended; | |
| 283 | 388 | // - offset below it: the first (count - offset) lines were already stored | |
| 284 | 389 | // (a retry after a lost response) and are skipped, and the rest is | |
| 285 | - | // appended; a request entirely stored changes nothing; | |
| 390 | + | // appended; a request entirely stored changes nothing. The skipped lines | |
| 391 | + | // are not compared with the stored ones: a retry whose lines differ from | |
| 392 | + | // the first attempt is silently dropped, so a client must resend the | |
| 393 | + | // same lines at the same offset; | |
| 286 | 394 | // - offset above it: out_of_range (a gap is a bug of the client; the | |
| 287 | 395 | // message gives the line count the server holds). | |
| 288 | 396 | // | |
| 289 | 397 | // 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. | |
| 398 | + | // itself. Offsets resume only after a network error within the same runner | |
| 399 | + | // process; they are not a way to resume a job after a crash. | |
| 400 | + | // | |
| 401 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |
| 402 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |
| 403 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |
| 404 | + | // | |
| 405 | + | // Steps. The step of a request must be at least the step of the last line | |
| 406 | + | // the server holds, because the log is ordered by step (applied lines only: | |
| 407 | + | // lines skipped as already stored are not checked); a lower step is | |
| 408 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |
| 409 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |
| 410 | + | // empty name until an UpdateJob gives one. | |
| 293 | 411 | // | |
| 294 | 412 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | |
| 295 | 413 | // the server (text, timestamps and separators). The call that would exceed | |
| 296 | 414 | // it appends nothing; the server adds a last line saying that the limit was | |
| 297 | 415 | // reached, ends the job with CI_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. | |
| 416 | + | // answers failed_precondition with a JobEnded detail like any call after the | |
| 417 | + | // end of a job. The runner then stops the job. | |
| 418 | + | // | |
| 419 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |
| 420 | + | // its AppendLog lines before it calls CompleteJob. | |
| 300 | 421 | // | |
| 301 | 422 | // Fails with invalid_argument if a rule is broken (step number, text with a | |
| 302 | - | // line feed or invalid UTF-8, missing time). | |
| 423 | + | // line feed or invalid UTF-8, missing time, a continued first line). | |
| 303 | 424 | AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) | |
| 304 | 425 | // 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 CI_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. | |
| 426 | + | // the job. Needs the job token. The first one acknowledges the assignment | |
| 427 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as | |
| 428 | + | // the job runs. The deadline is 2 minutes after the later of the last | |
| 429 | + | // heartbeat and the start of the server (so a server restart does not fail | |
| 430 | + | // the jobs of runners that could not reach it): past it the server ends the | |
| 431 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |
| 432 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |
| 433 | + | // other calls do not keep a job alive. | |
| 434 | + | // | |
| 435 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |
| 436 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |
| 437 | + | // second tick, and keeps the job running meanwhile. | |
| 310 | 438 | 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 | |
| 439 | + | // WatchJob streams what the runner must react to. Needs the job token. The | |
| 312 | 440 | // runner opens it when it starts the job and keeps it open; it reopens it | |
| 313 | - | // after any error other than unauthenticated. | |
| 441 | + | // after any error other than unauthenticated and the failed_precondition | |
| 442 | + | // that says the job ended, and after a clean close of | |
| 443 | + | // the stream that did not end with a JobEnded message. | |
| 314 | 444 | // | |
| 315 | 445 | // When the stream opens and a cancellation has already been requested, the | |
| 316 | 446 | // first message is that request, so a reconnect never misses one. Only the | |
| 317 | 447 | // 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 | |
| 448 | + | // second message when its time budget expires. The server also sends a | |
| 449 | + | // Keepalive at least every 30 seconds, which the client ignores. When the | |
| 450 | + | // job ends the server sends JobEnded, then closes the stream without error. | |
| 451 | + | // On a job that has already ended the call fails like the other RPCs | |
| 452 | + | // (failed_precondition with a JobEnded detail). | |
| 453 | + | // | |
| 454 | + | // On a CancelRequest the runner stops the job as the official runner does | |
| 322 | 455 | // (interrupt, then terminate, then kill) and reports the conclusion given | |
| 323 | - | // for the reason with UpdateJob. | |
| 456 | + | // for the reason with CompleteJob. | |
| 457 | + | // | |
| 458 | + | // The server does not trust the runner to obey. If no conclusion arrives | |
| 459 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |
| 460 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |
| 461 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |
| 462 | + | // saying that the runner did not answer, and the steps not completed are | |
| 463 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |
| 324 | 464 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error | |
| 465 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |
| 466 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |
| 467 | + | // hours after the end of the job, however it ended. The call is applied whole | |
| 468 | + | // or not at all. Once the job has ended, the token works for no other RPC | |
| 469 | + | // (they answer failed_precondition with a JobEnded detail). | |
| 470 | + | // | |
| 471 | + | // It is idempotent. The first call that finds the job running ends it and | |
| 472 | + | // answers applied = true. Any later call, a retry after a lost response or a | |
| 473 | + | // call after the server ended the job itself, is not validated and changes | |
| 474 | + | // nothing: it answers OK with the stored conclusion and error and | |
| 475 | + | // applied = false (a runner whose conclusion differs from the stored one | |
| 476 | + | // simply logs it; the outputs it sent are dropped). | |
| 477 | + | // | |
| 478 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |
| 479 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |
| 480 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |
| 481 | + | // they never started and the job did not end by cancellation). | |
| 482 | + | // | |
| 483 | + | // Fails with invalid_argument if a rule is broken (including the output | |
| 484 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |
| 485 | + | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | |
| 325 | 486 | } | |
| 326 | 487 | ||
| 327 | 488 | // NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the | |
| @@ -367,6 +528,12 @@ func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOp | |||
| 367 | 528 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | |
| 368 | 529 | connect.WithHandlerOptions(opts...), | |
| 369 | 530 | ) | |
| 531 | + | runnerServiceCompleteJobHandler := connect.NewUnaryHandler( | |
| 532 | + | RunnerServiceCompleteJobProcedure, | |
| 533 | + | svc.CompleteJob, | |
| 534 | + | connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")), | |
| 535 | + | connect.WithHandlerOptions(opts...), | |
| 536 | + | ) | |
| 370 | 537 | return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |
| 371 | 538 | switch r.URL.Path { | |
| 372 | 539 | case RunnerServiceRegisterRunnerProcedure: | |
| @@ -381,6 +548,8 @@ func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOp | |||
| 381 | 548 | runnerServiceHeartbeatHandler.ServeHTTP(w, r) | |
| 382 | 549 | case RunnerServiceWatchJobProcedure: | |
| 383 | 550 | runnerServiceWatchJobHandler.ServeHTTP(w, r) | |
| 551 | + | case RunnerServiceCompleteJobProcedure: | |
| 552 | + | runnerServiceCompleteJobHandler.ServeHTTP(w, r) | |
| 384 | 553 | default: | |
| 385 | 554 | http.NotFound(w, r) | |
| 386 | 555 | } | |
| @@ -413,3 +582,7 @@ func (UnimplementedRunnerServiceHandler) Heartbeat(context.Context, *connect.Req | |||
| 413 | 582 | func (UnimplementedRunnerServiceHandler) WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error { | |
| 414 | 583 | return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.WatchJob is not implemented")) | |
| 415 | 584 | } | |
| 585 | + | ||
| 586 | + | func (UnimplementedRunnerServiceHandler) CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | |
| 587 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.CompleteJob is not implemented")) | |
| 588 | + | } | |
| @@ -48,6 +48,9 @@ const ( | |||||
| 48 | RunnerServiceHeartbeatProcedure = "/ermite.RunnerService/Heartbeat" | 48 | RunnerServiceHeartbeatProcedure = "/ermite.RunnerService/Heartbeat" | ||
| 49 | // RunnerServiceWatchJobProcedure is the fully-qualified name of the RunnerService's WatchJob RPC. | 49 | // RunnerServiceWatchJobProcedure is the fully-qualified name of the RunnerService's WatchJob RPC. | ||
| 50 | RunnerServiceWatchJobProcedure = "/ermite.RunnerService/WatchJob" | 50 | RunnerServiceWatchJobProcedure = "/ermite.RunnerService/WatchJob" | ||
| 51 | + | // RunnerServiceCompleteJobProcedure is the fully-qualified name of the RunnerService's CompleteJob | |||
| 52 | + | // RPC. | |||
| 53 | + | RunnerServiceCompleteJobProcedure = "/ermite.RunnerService/CompleteJob" | |||
| 51 | ) | 54 | ) | ||
| 52 | 55 | ||||
| 53 | // RunnerServiceClient is a client for the ermite.RunnerService service. | 56 | // RunnerServiceClient is a client for the ermite.RunnerService service. | ||
| @@ -58,37 +61,54 @@ type RunnerServiceClient interface { | |||||
| 58 | // (a runner serves every repository), or the root token. Fails with | 61 | // (a runner serves every repository), or the root token. Fails with | ||
| 59 | // unauthenticated without a valid token, permission_denied without the | 62 | // unauthenticated without a valid token, permission_denied without the | ||
| 60 | // scope or with a repository-restricted token, invalid_argument if a field | 63 | // 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. | 64 | + | // breaks a rule, and already_exists if name is taken by another runner and |
| 65 | + | // replace is not set. | |||
| 62 | RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) | 66 | 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 | 67 | // 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 | 68 | // 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 | 69 | // 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 | 70 | + | // runner calls again after retry_after. Clients set a call timeout above 50 |
| 67 | - | // (60 is suggested) and must not poll when they have no free capacity. | 71 | + | // seconds (60 is suggested) and must not poll when they have no free |
| 72 | + | // capacity. | |||
| 68 | // | 73 | // | ||
| 69 | // A job matches when every label of its runs-on is among the labels of the | 74 | // 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 | 75 | // 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 | 76 | // 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 | 77 | + | // the response is sent. The job stays QUEUED for the owner until it is |
| 73 | - | // lost the job is not offered again: with no Heartbeat for 2 minutes the | 78 | + | // acknowledged. |
| 74 | - | // server fails it as a lost runner. | 79 | + | // |
| 80 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |||
| 81 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |||
| 82 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |||
| 83 | + | // response was lost, or the runner died), the server cancels the assignment: | |||
| 84 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |||
| 85 | + | // and can be given to any runner, the same one included; nothing the old | |||
| 86 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |||
| 87 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |||
| 88 | + | // and an error saying so. | |||
| 89 | + | // | |||
| 90 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |||
| 91 | + | // a call with the same poll_id as an earlier call of the same runner that | |||
| 92 | + | // assigned a job returns that same assignment (same Job, same token) as long | |||
| 93 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |||
| 94 | + | // | |||
| 95 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |||
| 96 | + | // not keep the job token, so the job fails with the lost-runner error after | |||
| 97 | + | // the heartbeat timeout (see Heartbeat). | |||
| 75 | // | 98 | // | ||
| 76 | // Needs the runner token. Fails with unauthenticated when the runner was | 99 | // Needs the runner token. Fails with unauthenticated when the runner was | ||
| 77 | // removed (also ending a call in progress) and invalid_argument if labels | 100 | // removed (also ending a call in progress) and invalid_argument if labels | ||
| 78 | - | // breaks a rule. | 101 | + | // or poll_id break a rule. |
| 79 | FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) | 102 | 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 | 103 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the |
| 81 | - | // ended. Needs the job token. Each call is applied whole or not at all, and | 104 | + | // job token. Each call is applied whole or not at all, and repeating an |
| 82 | - | // repeating an identical call changes nothing, so a runner may retry after a | 105 | + | // identical call changes nothing, so a runner may retry after a network |
| 83 | - | // network error. | 106 | + | // error. To end the job use CompleteJob. |
| 84 | // | 107 | // | ||
| 85 | - | // Fails with invalid_argument if a rule is broken (including the output | 108 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition |
| 86 | - | // limits), and with failed_precondition if a step goes back (a COMPLETED | 109 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from |
| 87 | - | // step cannot change, a step cannot go from IN_PROGRESS to PENDING). | 110 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail |
| 88 | - | // The call that carries a conclusion ends the job: its job token stops | 111 | + | // if the job has ended. |
| 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) | 112 | 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. | 113 | // AppendLog appends lines to the log of a job. Needs the job token. | ||
| 94 | // | 114 | // | ||
| @@ -100,46 +120,102 @@ type RunnerServiceClient interface { | |||||
| 100 | // - offset equal to the server's line count: the lines are appended; | 120 | // - offset equal to the server's line count: the lines are appended; | ||
| 101 | // - offset below it: the first (count - offset) lines were already stored | 121 | // - 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 | 122 | // (a retry after a lost response) and are skipped, and the rest is | ||
| 103 | - | // appended; a request entirely stored changes nothing; | 123 | + | // appended; a request entirely stored changes nothing. The skipped lines |
| 124 | + | // are not compared with the stored ones: a retry whose lines differ from | |||
| 125 | + | // the first attempt is silently dropped, so a client must resend the | |||
| 126 | + | // same lines at the same offset; | |||
| 104 | // - offset above it: out_of_range (a gap is a bug of the client; the | 127 | // - offset above it: out_of_range (a gap is a bug of the client; the | ||
| 105 | // message gives the line count the server holds). | 128 | // message gives the line count the server holds). | ||
| 106 | // | 129 | // | ||
| 107 | // The response gives the line count after the call, so a client can check | 130 | // 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 | 131 | + | // itself. Offsets resume only after a network error within the same runner |
| 109 | - | // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A | 132 | + | // process; they are not a way to resume a job after a crash. |
| 110 | - | // line of more than 64 KiB is cut by the client into several lines. | 133 | + | // |
| 134 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |||
| 135 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |||
| 136 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |||
| 137 | + | // | |||
| 138 | + | // Steps. The step of a request must be at least the step of the last line | |||
| 139 | + | // the server holds, because the log is ordered by step (applied lines only: | |||
| 140 | + | // lines skipped as already stored are not checked); a lower step is | |||
| 141 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |||
| 142 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |||
| 143 | + | // empty name until an UpdateJob gives one. | |||
| 111 | // | 144 | // | ||
| 112 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | 145 | // 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 | 146 | // 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 | 147 | // it appends nothing; the server adds a last line saying that the limit was | ||
| 115 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | 148 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | ||
| 116 | - | // answers resource_exhausted. The runner then stops the job and sends | 149 | + | // answers failed_precondition with a JobEnded detail like any call after the |
| 117 | - | // nothing more: its calls get unauthenticated. | 150 | + | // end of a job. The runner then stops the job. |
| 151 | + | // | |||
| 152 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |||
| 153 | + | // its AppendLog lines before it calls CompleteJob. | |||
| 118 | // | 154 | // | ||
| 119 | // Fails with invalid_argument if a rule is broken (step number, text with a | 155 | // Fails with invalid_argument if a rule is broken (step number, text with a | ||
| 120 | - | // line feed or invalid UTF-8, missing time). | 156 | + | // line feed or invalid UTF-8, missing time, a continued first line). |
| 121 | AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) | 157 | 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 | 158 | // 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 | 159 | + | // the job. Needs the job token. The first one acknowledges the assignment |
| 124 | - | // long as the job runs, from assignment to the end. Without a heartbeat for | 160 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as |
| 125 | - | // 2 minutes the server ends the job with CI_CONCLUSION_FAILURE and the message | 161 | + | // the job runs. The deadline is 2 minutes after the later of the last |
| 126 | - | // "The self-hosted runner: <name> lost communication with the server...". | 162 | + | // heartbeat and the start of the server (so a server restart does not fail |
| 127 | - | // Only Heartbeat counts: other calls do not keep a job alive. | 163 | + | // the jobs of runners that could not reach it): past it the server ends the |
| 164 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |||
| 165 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |||
| 166 | + | // other calls do not keep a job alive. | |||
| 167 | + | // | |||
| 168 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |||
| 169 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |||
| 170 | + | // second tick, and keeps the job running meanwhile. | |||
| 128 | Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) | 171 | 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 | 172 | + | // WatchJob streams what the runner must react to. Needs the job token. The |
| 130 | // runner opens it when it starts the job and keeps it open; it reopens it | 173 | // runner opens it when it starts the job and keeps it open; it reopens it | ||
| 131 | - | // after any error other than unauthenticated. | 174 | + | // after any error other than unauthenticated and the failed_precondition |
| 175 | + | // that says the job ended, and after a clean close of | |||
| 176 | + | // the stream that did not end with a JobEnded message. | |||
| 132 | // | 177 | // | ||
| 133 | // When the stream opens and a cancellation has already been requested, the | 178 | // 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 | 179 | // 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 | 180 | // 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 | 181 | + | // second message when its time budget expires. The server also sends a |
| 137 | - | // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30 | 182 | + | // Keepalive at least every 30 seconds, which the client ignores. When the |
| 138 | - | // seconds, which the client ignores. The stream ends without error when | 183 | + | // job ends the server sends JobEnded, then closes the stream without error. |
| 139 | - | // the job ends. The runner stops the job as the official runner does | 184 | + | // On a job that has already ended the call fails like the other RPCs |
| 185 | + | // (failed_precondition with a JobEnded detail). | |||
| 186 | + | // | |||
| 187 | + | // On a CancelRequest the runner stops the job as the official runner does | |||
| 140 | // (interrupt, then terminate, then kill) and reports the conclusion given | 188 | // (interrupt, then terminate, then kill) and reports the conclusion given | ||
| 141 | - | // for the reason with UpdateJob. | 189 | + | // for the reason with CompleteJob. |
| 190 | + | // | |||
| 191 | + | // The server does not trust the runner to obey. If no conclusion arrives | |||
| 192 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |||
| 193 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |||
| 194 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |||
| 195 | + | // saying that the runner did not answer, and the steps not completed are | |||
| 196 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |||
| 142 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | 197 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | ||
| 198 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |||
| 199 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |||
| 200 | + | // hours after the end of the job, however it ended. The call is applied whole | |||
| 201 | + | // or not at all. Once the job has ended, the token works for no other RPC | |||
| 202 | + | // (they answer failed_precondition with a JobEnded detail). | |||
| 203 | + | // | |||
| 204 | + | // It is idempotent. The first call that finds the job running ends it and | |||
| 205 | + | // answers applied = true. Any later call, a retry after a lost response or a | |||
| 206 | + | // call after the server ended the job itself, is not validated and changes | |||
| 207 | + | // nothing: it answers OK with the stored conclusion and error and | |||
| 208 | + | // applied = false (a runner whose conclusion differs from the stored one | |||
| 209 | + | // simply logs it; the outputs it sent are dropped). | |||
| 210 | + | // | |||
| 211 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |||
| 212 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |||
| 213 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |||
| 214 | + | // they never started and the job did not end by cancellation). | |||
| 215 | + | // | |||
| 216 | + | // Fails with invalid_argument if a rule is broken (including the output | |||
| 217 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |||
| 218 | + | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | |||
| 143 | } | 219 | } | ||
| 144 | 220 | ||||
| 145 | // NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it | 221 | // NewRunnerServiceClient constructs a client for the ermite.RunnerService service. By default, it | ||
| @@ -189,6 +265,12 @@ func NewRunnerServiceClient(httpClient connect.HTTPClient, baseURL string, opts | |||||
| 189 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | 265 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | ||
| 190 | connect.WithClientOptions(opts...), | 266 | connect.WithClientOptions(opts...), | ||
| 191 | ), | 267 | ), | ||
| 268 | + | completeJob: connect.NewClient[ermite.CompleteJobRequest, ermite.CompleteJobResponse]( | |||
| 269 | + | httpClient, | |||
| 270 | + | baseURL+RunnerServiceCompleteJobProcedure, | |||
| 271 | + | connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")), | |||
| 272 | + | connect.WithClientOptions(opts...), | |||
| 273 | + | ), | |||
| 192 | } | 274 | } | ||
| 193 | } | 275 | } | ||
| 194 | 276 | ||||
| @@ -200,6 +282,7 @@ type runnerServiceClient struct { | |||||
| 200 | appendLog *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse] | 282 | appendLog *connect.Client[ermite.AppendLogRequest, ermite.AppendLogResponse] | ||
| 201 | heartbeat *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse] | 283 | heartbeat *connect.Client[ermite.HeartbeatRequest, ermite.HeartbeatResponse] | ||
| 202 | watchJob *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse] | 284 | watchJob *connect.Client[ermite.WatchJobRequest, ermite.WatchJobResponse] | ||
| 285 | + | completeJob *connect.Client[ermite.CompleteJobRequest, ermite.CompleteJobResponse] | |||
| 203 | } | 286 | } | ||
| 204 | 287 | ||||
| 205 | // RegisterRunner calls ermite.RunnerService.RegisterRunner. | 288 | // RegisterRunner calls ermite.RunnerService.RegisterRunner. | ||
| @@ -232,6 +315,11 @@ func (c *runnerServiceClient) WatchJob(ctx context.Context, req *connect.Request | |||||
| 232 | return c.watchJob.CallServerStream(ctx, req) | 315 | return c.watchJob.CallServerStream(ctx, req) | ||
| 233 | } | 316 | } | ||
| 234 | 317 | ||||
| 318 | + | // CompleteJob calls ermite.RunnerService.CompleteJob. | |||
| 319 | + | func (c *runnerServiceClient) CompleteJob(ctx context.Context, req *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | |||
| 320 | + | return c.completeJob.CallUnary(ctx, req) | |||
| 321 | + | } | |||
| 322 | + | ||||
| 235 | // RunnerServiceHandler is an implementation of the ermite.RunnerService service. | 323 | // RunnerServiceHandler is an implementation of the ermite.RunnerService service. | ||
| 236 | type RunnerServiceHandler interface { | 324 | type RunnerServiceHandler interface { | ||
| 237 | // RegisterRunner registers a runner and returns its runner token, which is | 325 | // RegisterRunner registers a runner and returns its runner token, which is | ||
| @@ -240,37 +328,54 @@ type RunnerServiceHandler interface { | |||||
| 240 | // (a runner serves every repository), or the root token. Fails with | 328 | // (a runner serves every repository), or the root token. Fails with | ||
| 241 | // unauthenticated without a valid token, permission_denied without the | 329 | // unauthenticated without a valid token, permission_denied without the | ||
| 242 | // scope or with a repository-restricted token, invalid_argument if a field | 330 | // 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. | 331 | + | // breaks a rule, and already_exists if name is taken by another runner and |
| 332 | + | // replace is not set. | |||
| 244 | RegisterRunner(context.Context, *connect.Request[ermite.RegisterRunnerRequest]) (*connect.Response[ermite.RegisterRunnerResponse], error) | 333 | 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 | 334 | // 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 | 335 | // 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 | 336 | // 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 | 337 | + | // runner calls again after retry_after. Clients set a call timeout above 50 |
| 249 | - | // (60 is suggested) and must not poll when they have no free capacity. | 338 | + | // seconds (60 is suggested) and must not poll when they have no free |
| 339 | + | // capacity. | |||
| 250 | // | 340 | // | ||
| 251 | // A job matches when every label of its runs-on is among the labels of the | 341 | // 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 | 342 | // 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 | 343 | // 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 | 344 | + | // the response is sent. The job stays QUEUED for the owner until it is |
| 255 | - | // lost the job is not offered again: with no Heartbeat for 2 minutes the | 345 | + | // acknowledged. |
| 256 | - | // server fails it as a lost runner. | 346 | + | // |
| 347 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |||
| 348 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |||
| 349 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |||
| 350 | + | // response was lost, or the runner died), the server cancels the assignment: | |||
| 351 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |||
| 352 | + | // and can be given to any runner, the same one included; nothing the old | |||
| 353 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |||
| 354 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |||
| 355 | + | // and an error saying so. | |||
| 356 | + | // | |||
| 357 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |||
| 358 | + | // a call with the same poll_id as an earlier call of the same runner that | |||
| 359 | + | // assigned a job returns that same assignment (same Job, same token) as long | |||
| 360 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |||
| 361 | + | // | |||
| 362 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |||
| 363 | + | // not keep the job token, so the job fails with the lost-runner error after | |||
| 364 | + | // the heartbeat timeout (see Heartbeat). | |||
| 257 | // | 365 | // | ||
| 258 | // Needs the runner token. Fails with unauthenticated when the runner was | 366 | // Needs the runner token. Fails with unauthenticated when the runner was | ||
| 259 | // removed (also ending a call in progress) and invalid_argument if labels | 367 | // removed (also ending a call in progress) and invalid_argument if labels | ||
| 260 | - | // breaks a rule. | 368 | + | // or poll_id break a rule. |
| 261 | FetchJob(context.Context, *connect.Request[ermite.FetchJobRequest]) (*connect.Response[ermite.FetchJobResponse], error) | 369 | 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 | 370 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the |
| 263 | - | // ended. Needs the job token. Each call is applied whole or not at all, and | 371 | + | // job token. Each call is applied whole or not at all, and repeating an |
| 264 | - | // repeating an identical call changes nothing, so a runner may retry after a | 372 | + | // identical call changes nothing, so a runner may retry after a network |
| 265 | - | // network error. | 373 | + | // error. To end the job use CompleteJob. |
| 266 | // | 374 | // | ||
| 267 | - | // Fails with invalid_argument if a rule is broken (including the output | 375 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition |
| 268 | - | // limits), and with failed_precondition if a step goes back (a COMPLETED | 376 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from |
| 269 | - | // step cannot change, a step cannot go from IN_PROGRESS to PENDING). | 377 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail |
| 270 | - | // The call that carries a conclusion ends the job: its job token stops | 378 | + | // if the job has ended. |
| 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) | 379 | 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. | 380 | // AppendLog appends lines to the log of a job. Needs the job token. | ||
| 276 | // | 381 | // | ||
| @@ -282,46 +387,102 @@ type RunnerServiceHandler interface { | |||||
| 282 | // - offset equal to the server's line count: the lines are appended; | 387 | // - offset equal to the server's line count: the lines are appended; | ||
| 283 | // - offset below it: the first (count - offset) lines were already stored | 388 | // - 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 | 389 | // (a retry after a lost response) and are skipped, and the rest is | ||
| 285 | - | // appended; a request entirely stored changes nothing; | 390 | + | // appended; a request entirely stored changes nothing. The skipped lines |
| 391 | + | // are not compared with the stored ones: a retry whose lines differ from | |||
| 392 | + | // the first attempt is silently dropped, so a client must resend the | |||
| 393 | + | // same lines at the same offset; | |||
| 286 | // - offset above it: out_of_range (a gap is a bug of the client; the | 394 | // - offset above it: out_of_range (a gap is a bug of the client; the | ||
| 287 | // message gives the line count the server holds). | 395 | // message gives the line count the server holds). | ||
| 288 | // | 396 | // | ||
| 289 | // The response gives the line count after the call, so a client can check | 397 | // 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 | 398 | + | // itself. Offsets resume only after a network error within the same runner |
| 291 | - | // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A | 399 | + | // process; they are not a way to resume a job after a crash. |
| 292 | - | // line of more than 64 KiB is cut by the client into several lines. | 400 | + | // |
| 401 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |||
| 402 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |||
| 403 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |||
| 404 | + | // | |||
| 405 | + | // Steps. The step of a request must be at least the step of the last line | |||
| 406 | + | // the server holds, because the log is ordered by step (applied lines only: | |||
| 407 | + | // lines skipped as already stored are not checked); a lower step is | |||
| 408 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |||
| 409 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |||
| 410 | + | // empty name until an UpdateJob gives one. | |||
| 293 | // | 411 | // | ||
| 294 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | 412 | // 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 | 413 | // 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 | 414 | // it appends nothing; the server adds a last line saying that the limit was | ||
| 297 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | 415 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | ||
| 298 | - | // answers resource_exhausted. The runner then stops the job and sends | 416 | + | // answers failed_precondition with a JobEnded detail like any call after the |
| 299 | - | // nothing more: its calls get unauthenticated. | 417 | + | // end of a job. The runner then stops the job. |
| 418 | + | // | |||
| 419 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |||
| 420 | + | // its AppendLog lines before it calls CompleteJob. | |||
| 300 | // | 421 | // | ||
| 301 | // Fails with invalid_argument if a rule is broken (step number, text with a | 422 | // Fails with invalid_argument if a rule is broken (step number, text with a | ||
| 302 | - | // line feed or invalid UTF-8, missing time). | 423 | + | // line feed or invalid UTF-8, missing time, a continued first line). |
| 303 | AppendLog(context.Context, *connect.Request[ermite.AppendLogRequest]) (*connect.Response[ermite.AppendLogResponse], error) | 424 | 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 | 425 | // 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 | 426 | + | // the job. Needs the job token. The first one acknowledges the assignment |
| 306 | - | // long as the job runs, from assignment to the end. Without a heartbeat for | 427 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as |
| 307 | - | // 2 minutes the server ends the job with CI_CONCLUSION_FAILURE and the message | 428 | + | // the job runs. The deadline is 2 minutes after the later of the last |
| 308 | - | // "The self-hosted runner: <name> lost communication with the server...". | 429 | + | // heartbeat and the start of the server (so a server restart does not fail |
| 309 | - | // Only Heartbeat counts: other calls do not keep a job alive. | 430 | + | // the jobs of runners that could not reach it): past it the server ends the |
| 431 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |||
| 432 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |||
| 433 | + | // other calls do not keep a job alive. | |||
| 434 | + | // | |||
| 435 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |||
| 436 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |||
| 437 | + | // second tick, and keeps the job running meanwhile. | |||
| 310 | Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) | 438 | 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 | 439 | + | // WatchJob streams what the runner must react to. Needs the job token. The |
| 312 | // runner opens it when it starts the job and keeps it open; it reopens it | 440 | // runner opens it when it starts the job and keeps it open; it reopens it | ||
| 313 | - | // after any error other than unauthenticated. | 441 | + | // after any error other than unauthenticated and the failed_precondition |
| 442 | + | // that says the job ended, and after a clean close of | |||
| 443 | + | // the stream that did not end with a JobEnded message. | |||
| 314 | // | 444 | // | ||
| 315 | // When the stream opens and a cancellation has already been requested, the | 445 | // 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 | 446 | // 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 | 447 | // 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 | 448 | + | // second message when its time budget expires. The server also sends a |
| 319 | - | // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30 | 449 | + | // Keepalive at least every 30 seconds, which the client ignores. When the |
| 320 | - | // seconds, which the client ignores. The stream ends without error when | 450 | + | // job ends the server sends JobEnded, then closes the stream without error. |
| 321 | - | // the job ends. The runner stops the job as the official runner does | 451 | + | // On a job that has already ended the call fails like the other RPCs |
| 452 | + | // (failed_precondition with a JobEnded detail). | |||
| 453 | + | // | |||
| 454 | + | // On a CancelRequest the runner stops the job as the official runner does | |||
| 322 | // (interrupt, then terminate, then kill) and reports the conclusion given | 455 | // (interrupt, then terminate, then kill) and reports the conclusion given | ||
| 323 | - | // for the reason with UpdateJob. | 456 | + | // for the reason with CompleteJob. |
| 457 | + | // | |||
| 458 | + | // The server does not trust the runner to obey. If no conclusion arrives | |||
| 459 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |||
| 460 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |||
| 461 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |||
| 462 | + | // saying that the runner did not answer, and the steps not completed are | |||
| 463 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |||
| 324 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error | 464 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error | ||
| 465 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |||
| 466 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |||
| 467 | + | // hours after the end of the job, however it ended. The call is applied whole | |||
| 468 | + | // or not at all. Once the job has ended, the token works for no other RPC | |||
| 469 | + | // (they answer failed_precondition with a JobEnded detail). | |||
| 470 | + | // | |||
| 471 | + | // It is idempotent. The first call that finds the job running ends it and | |||
| 472 | + | // answers applied = true. Any later call, a retry after a lost response or a | |||
| 473 | + | // call after the server ended the job itself, is not validated and changes | |||
| 474 | + | // nothing: it answers OK with the stored conclusion and error and | |||
| 475 | + | // applied = false (a runner whose conclusion differs from the stored one | |||
| 476 | + | // simply logs it; the outputs it sent are dropped). | |||
| 477 | + | // | |||
| 478 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |||
| 479 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |||
| 480 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |||
| 481 | + | // they never started and the job did not end by cancellation). | |||
| 482 | + | // | |||
| 483 | + | // Fails with invalid_argument if a rule is broken (including the output | |||
| 484 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |||
| 485 | + | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | |||
| 325 | } | 486 | } | ||
| 326 | 487 | ||||
| 327 | // NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the | 488 | // NewRunnerServiceHandler builds an HTTP handler from the service implementation. It returns the | ||
| @@ -367,6 +528,12 @@ func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOp | |||||
| 367 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | 528 | connect.WithSchema(runnerServiceMethods.ByName("WatchJob")), | ||
| 368 | connect.WithHandlerOptions(opts...), | 529 | connect.WithHandlerOptions(opts...), | ||
| 369 | ) | 530 | ) | ||
| 531 | + | runnerServiceCompleteJobHandler := connect.NewUnaryHandler( | |||
| 532 | + | RunnerServiceCompleteJobProcedure, | |||
| 533 | + | svc.CompleteJob, | |||
| 534 | + | connect.WithSchema(runnerServiceMethods.ByName("CompleteJob")), | |||
| 535 | + | connect.WithHandlerOptions(opts...), | |||
| 536 | + | ) | |||
| 370 | return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | 537 | return "/ermite.RunnerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| 371 | switch r.URL.Path { | 538 | switch r.URL.Path { | ||
| 372 | case RunnerServiceRegisterRunnerProcedure: | 539 | case RunnerServiceRegisterRunnerProcedure: | ||
| @@ -381,6 +548,8 @@ func NewRunnerServiceHandler(svc RunnerServiceHandler, opts ...connect.HandlerOp | |||||
| 381 | runnerServiceHeartbeatHandler.ServeHTTP(w, r) | 548 | runnerServiceHeartbeatHandler.ServeHTTP(w, r) | ||
| 382 | case RunnerServiceWatchJobProcedure: | 549 | case RunnerServiceWatchJobProcedure: | ||
| 383 | runnerServiceWatchJobHandler.ServeHTTP(w, r) | 550 | runnerServiceWatchJobHandler.ServeHTTP(w, r) | ||
| 551 | + | case RunnerServiceCompleteJobProcedure: | |||
| 552 | + | runnerServiceCompleteJobHandler.ServeHTTP(w, r) | |||
| 384 | default: | 553 | default: | ||
| 385 | http.NotFound(w, r) | 554 | http.NotFound(w, r) | ||
| 386 | } | 555 | } | ||
| @@ -413,3 +582,7 @@ func (UnimplementedRunnerServiceHandler) Heartbeat(context.Context, *connect.Req | |||||
| 413 | func (UnimplementedRunnerServiceHandler) WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest], *connect.ServerStream[ermite.WatchJobResponse]) error { | 582 | 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")) | 583 | return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.WatchJob is not implemented")) | ||
| 415 | } | 584 | } | ||
| 585 | + | ||||
| 586 | + | func (UnimplementedRunnerServiceHandler) CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | |||
| 587 | + | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunnerService.CompleteJob is not implemented")) | |||
| 588 | + | } | |||
M gen/ermite/event.pb.go +1 -1 generated file: collapsed
M gen/ermite/run.pb.go +48 -8 generated file: collapsed
M gen/ermite/runner.pb.go +593 -164
| @@ -11,6 +11,7 @@ package ermite | |||
| 11 | 11 | import ( | |
| 12 | 12 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" | |
| 13 | 13 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" | |
| 14 | + | durationpb "google.golang.org/protobuf/types/known/durationpb" | |
| 14 | 15 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" | |
| 15 | 16 | reflect "reflect" | |
| 16 | 17 | sync "sync" | |
| @@ -287,8 +288,7 @@ func (RunnerIsolation) EnumDescriptor() ([]byte, []int) { | |||
| 287 | 288 | type CancelReason int32 | |
| 288 | 289 | ||
| 289 | 290 | const ( | |
| 290 | - | // Not set. A WatchJobResponse with this reason is a keep-alive and carries | |
| 291 | - | // no request. | |
| 291 | + | // Not set. Never sent in a CancelRequest. | |
| 292 | 292 | CancelReason_CANCEL_REASON_UNSPECIFIED CancelReason = 0 | |
| 293 | 293 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | |
| 294 | 294 | // ends CI_CONCLUSION_CANCELLED. | |
| @@ -296,8 +296,8 @@ const ( | |||
| 296 | 296 | // Another job of the same matrix failed and the strategy has fail-fast. The | |
| 297 | 297 | // job ends CI_CONCLUSION_CANCELLED. | |
| 298 | 298 | CancelReason_CANCEL_REASON_FAIL_FAST CancelReason = 2 | |
| 299 | - | // The job exceeded its timeout_minutes without the runner ending it. The | |
| 300 | - | // job ends CI_CONCLUSION_TIMED_OUT. | |
| 299 | + | // The job exceeded its time budget (see Job.timeout_minutes). The job ends | |
| 300 | + | // CI_CONCLUSION_TIMED_OUT. | |
| 301 | 301 | CancelReason_CANCEL_REASON_TIMEOUT CancelReason = 3 | |
| 302 | 302 | ) | |
| 303 | 303 | ||
| @@ -367,7 +367,14 @@ type RegisterRunnerRequest struct { | |||
| 367 | 367 | Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` | |
| 368 | 368 | // Os is the operating system: "linux". Other values are invalid_argument | |
| 369 | 369 | // (the set may grow with compatible additions). | |
| 370 | - | Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"` | |
| 370 | + | Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"` | |
| 371 | + | // Replace removes the runner that already has this name, if any, before | |
| 372 | + | // registering this one: its runner token is revoked at once, as in | |
| 373 | + | // RunService.RemoveRunner, and a job it is running is not affected. The new | |
| 374 | + | // runner gets a new runner_id. Needs the same scope as registering. Without | |
| 375 | + | // replace, a name in use is already_exists. It is how a runner re-registers | |
| 376 | + | // after losing its state. | |
| 377 | + | Replace bool `protobuf:"varint,7,opt,name=replace,proto3" json:"replace,omitempty"` | |
| 371 | 378 | unknownFields protoimpl.UnknownFields | |
| 372 | 379 | sizeCache protoimpl.SizeCache | |
| 373 | 380 | } | |
| @@ -444,6 +451,13 @@ func (x *RegisterRunnerRequest) GetOs() string { | |||
| 444 | 451 | return "" | |
| 445 | 452 | } | |
| 446 | 453 | ||
| 454 | + | func (x *RegisterRunnerRequest) GetReplace() bool { | |
| 455 | + | if x != nil { | |
| 456 | + | return x.Replace | |
| 457 | + | } | |
| 458 | + | return false | |
| 459 | + | } | |
| 460 | + | ||
| 447 | 461 | // RegisterRunnerResponse holds the new runner and its secret. | |
| 448 | 462 | type RegisterRunnerResponse struct { | |
| 449 | 463 | state protoimpl.MessageState `protogen:"open.v1"` | |
| @@ -451,9 +465,10 @@ type RegisterRunnerResponse struct { | |||
| 451 | 465 | // a secret. | |
| 452 | 466 | RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` | |
| 453 | 467 | // 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. | |
| 468 | + | // Bearer <runner_token>" to FetchJob: "ermiterun_" and 43 base64url | |
| 469 | + | // characters (32 random bytes, no padding). It is returned once: the server | |
| 470 | + | // keeps only its SHA-256, the runner must store it (mode 0600). It stops | |
| 471 | + | // working when the runner is removed or replaced. | |
| 457 | 472 | RunnerToken string `protobuf:"bytes,2,opt,name=runner_token,json=runnerToken,proto3" json:"runner_token,omitempty"` | |
| 458 | 473 | unknownFields protoimpl.UnknownFields | |
| 459 | 474 | sizeCache protoimpl.SizeCache | |
| @@ -510,7 +525,14 @@ type FetchJobRequest struct { | |||
| 510 | 525 | // labels of the runner, which is the normal case. Otherwise each one must be | |
| 511 | 526 | // a label of the runner (invalid_argument if not) and only jobs whose runs-on | |
| 512 | 527 | // labels are all among them are offered. | |
| 513 | - | Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` | |
| 528 | + | Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` | |
| 529 | + | // PollId identifies this poll, chosen by the client (for example 16 random | |
| 530 | + | // bytes in base64url), 1 to 64 characters among letters, digits, "_" and | |
| 531 | + | // "-". A client that retries a call whose response it did not get sends the | |
| 532 | + | // same poll_id, and gets the assignment the first call made, if it made one | |
| 533 | + | // (see FetchJob). A new poll uses a new id. Empty means no retry protection: | |
| 534 | + | // a lost assignment is then recovered by the acknowledgement timeout alone. | |
| 535 | + | PollId string `protobuf:"bytes,2,opt,name=poll_id,json=pollId,proto3" json:"poll_id,omitempty"` | |
| 514 | 536 | unknownFields protoimpl.UnknownFields | |
| 515 | 537 | sizeCache protoimpl.SizeCache | |
| 516 | 538 | } | |
| @@ -552,12 +574,24 @@ func (x *FetchJobRequest) GetLabels() []string { | |||
| 552 | 574 | return nil | |
| 553 | 575 | } | |
| 554 | 576 | ||
| 577 | + | func (x *FetchJobRequest) GetPollId() string { | |
| 578 | + | if x != nil { | |
| 579 | + | return x.PollId | |
| 580 | + | } | |
| 581 | + | return "" | |
| 582 | + | } | |
| 583 | + | ||
| 555 | 584 | // FetchJobResponse holds the job given to the runner, if any. | |
| 556 | 585 | type FetchJobResponse struct { | |
| 557 | 586 | state protoimpl.MessageState `protogen:"open.v1"` | |
| 558 | 587 | // Job is the job to run. Unset when no job was available before the end of | |
| 559 | 588 | // the poll. | |
| 560 | - | Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` | |
| 589 | + | Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` | |
| 590 | + | // RetryAfter is how long the client waits before its next FetchJob. Unset or | |
| 591 | + | // zero means call again at once. The server sets it when it has no job for | |
| 592 | + | // the runner and wants to slow polling down (load, shutdown in progress); | |
| 593 | + | // clients obey it and may wait longer, never less. Unset when job is set. | |
| 594 | + | RetryAfter *durationpb.Duration `protobuf:"bytes,2,opt,name=retry_after,json=retryAfter,proto3" json:"retry_after,omitempty"` | |
| 561 | 595 | unknownFields protoimpl.UnknownFields | |
| 562 | 596 | sizeCache protoimpl.SizeCache | |
| 563 | 597 | } | |
| @@ -599,6 +633,13 @@ func (x *FetchJobResponse) GetJob() *Job { | |||
| 599 | 633 | return nil | |
| 600 | 634 | } | |
| 601 | 635 | ||
| 636 | + | func (x *FetchJobResponse) GetRetryAfter() *durationpb.Duration { | |
| 637 | + | if x != nil { | |
| 638 | + | return x.RetryAfter | |
| 639 | + | } | |
| 640 | + | return nil | |
| 641 | + | } | |
| 642 | + | ||
| 602 | 643 | // Job is a job given to a runner: everything needed to run it, nothing more. | |
| 603 | 644 | // The runner parses workflow_yaml itself; the protocol does not carry the | |
| 604 | 645 | // workflow model, so it does not change when the model grows. | |
| @@ -615,10 +656,13 @@ type Job struct { | |||
| 615 | 656 | // one more after each RunService.RerunRun. It is the "github.run_attempt". | |
| 616 | 657 | Attempt int32 `protobuf:"varint,3,opt,name=attempt,proto3" json:"attempt,omitempty"` | |
| 617 | 658 | // 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. | |
| 659 | + | // AppendLog, Heartbeat, WatchJob and CompleteJob calls ("Authorization: | |
| 660 | + | // Bearer <job_token>"): "ermitejob_" and 43 base64url characters (32 random | |
| 661 | + | // bytes, no padding). It is random, valid from the assignment until the job | |
| 662 | + | // ends (and for CompleteJob 24 hours more), survives a server restart and | |
| 663 | + | // is shown only in this response, or in a repeat of the same poll_id. It | |
| 664 | + | // must stay in the runner host process and never reach the job's code. A new | |
| 665 | + | // assignment of the same job has a new token. | |
| 622 | 666 | JobToken string `protobuf:"bytes,4,opt,name=job_token,json=jobToken,proto3" json:"job_token,omitempty"` | |
| 623 | 667 | // WorkflowPath is the path of the workflow file in the repository, for | |
| 624 | 668 | // example ".github/workflows/ci.yml". | |
| @@ -643,8 +687,11 @@ type Job struct { | |||
| 643 | 687 | Labels []string `protobuf:"bytes,10,rep,name=labels,proto3" json:"labels,omitempty"` | |
| 644 | 688 | // TimeoutMinutes is the timeout of the job in minutes: the value of the | |
| 645 | 689 | // 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. | |
| 690 | + | // runner enforces it from the moment it receives the job. The server counts | |
| 691 | + | // from the moment of the assignment and adds 60 seconds of slack, so the | |
| 692 | + | // runner's own timeout always comes first: when the server's deadline | |
| 693 | + | // passes it announces CANCEL_REASON_TIMEOUT, and ends the job itself after | |
| 694 | + | // 60 more seconds without a conclusion (see WatchJob). | |
| 648 | 695 | TimeoutMinutes int32 `protobuf:"varint,11,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"` | |
| 649 | 696 | // Repo is the name of the repository. | |
| 650 | 697 | Repo string `protobuf:"bytes,12,opt,name=repo,proto3" json:"repo,omitempty"` | |
| @@ -786,7 +833,7 @@ func (x *Job) GetRef() string { | |||
| 786 | 833 | return "" | |
| 787 | 834 | } | |
| 788 | 835 | ||
| 789 | - | // UpdateJobRequest reports the steps and, in the last call, the end of a job. | |
| 836 | + | // UpdateJobRequest reports the steps of a running job. | |
| 790 | 837 | type UpdateJobRequest struct { | |
| 791 | 838 | state protoimpl.MessageState `protogen:"open.v1"` | |
| 792 | 839 | // JobId is the Job.job_id. It must be the job of the token. | |
| @@ -796,31 +843,9 @@ type UpdateJobRequest struct { | |||
| 796 | 843 | // (invalid_argument beyond). Steps are numbered like GitHub's log: the | |
| 797 | 844 | // implicit "Set up job" is 1, the steps of the workflow follow in order, and | |
| 798 | 845 | // 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 CI_CONCLUSION_SUCCESS, | |
| 810 | - | // CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or CI_CONCLUSION_TIMED_OUT; any | |
| 811 | - | // other value is invalid_argument. CI_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 CI_CONCLUSION_CANCELLED (or SKIPPED when they | |
| 814 | - | // never started and the job did not fail by cancellation). | |
| 815 | - | Conclusion CiConclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.CiConclusion" 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 CI_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 | |
| 846 | + | Steps []*StepUpdate `protobuf:"bytes,2,rep,name=steps,proto3" json:"steps,omitempty"` | |
| 847 | + | unknownFields protoimpl.UnknownFields | |
| 848 | + | sizeCache protoimpl.SizeCache | |
| 824 | 849 | } | |
| 825 | 850 | ||
| 826 | 851 | func (x *UpdateJobRequest) Reset() { | |
| @@ -867,27 +892,6 @@ func (x *UpdateJobRequest) GetSteps() []*StepUpdate { | |||
| 867 | 892 | return nil | |
| 868 | 893 | } | |
| 869 | 894 | ||
| 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() CiConclusion { | |
| 878 | - | if x != nil { | |
| 879 | - | return x.Conclusion | |
| 880 | - | } | |
| 881 | - | return CiConclusion_CI_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 | 895 | // StepUpdate is the state of one step as the runner knows it. | |
| 892 | 896 | type StepUpdate struct { | |
| 893 | 897 | state protoimpl.MessageState `protogen:"open.v1"` | |
| @@ -1023,14 +1027,237 @@ func (*UpdateJobResponse) Descriptor() ([]byte, []int) { | |||
| 1023 | 1027 | return file_ermite_runner_proto_rawDescGZIP(), []int{7} | |
| 1024 | 1028 | } | |
| 1025 | 1029 | ||
| 1030 | + | // CompleteJobRequest ends a job. | |
| 1031 | + | type CompleteJobRequest struct { | |
| 1032 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1033 | + | // JobId is the Job.job_id. It must be the job of the token. | |
| 1034 | + | JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` | |
| 1035 | + | // Conclusion is how the job ended. Required: a runner reports | |
| 1036 | + | // CI_CONCLUSION_SUCCESS, CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or | |
| 1037 | + | // CI_CONCLUSION_TIMED_OUT; any other value is invalid_argument. Skipped is | |
| 1038 | + | // decided by the server and never reported by a runner. | |
| 1039 | + | Conclusion CiConclusion `protobuf:"varint,2,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |
| 1040 | + | // Outputs are the outputs of the job, evaluated by the runner. Names are 1 | |
| 1041 | + | // to 255 characters among letters, digits, "_" and "-" and begin with a | |
| 1042 | + | // letter or "_" and are case-insensitive: two names that differ only by case | |
| 1043 | + | // are invalid_argument. Each value is at most 1 MiB, and all names and | |
| 1044 | + | // values together at most 1 MiB (1,048,576 bytes); beyond that the whole call | |
| 1045 | + | // is rejected with invalid_argument and the runner reports the failure of | |
| 1046 | + | // the job without outputs, with a line in the log. | |
| 1047 | + | 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"` | |
| 1048 | + | // RunnerShutdown is true when the runner ends the job because it was told to | |
| 1049 | + | // stop (service stopped, signal). Only allowed with CI_CONCLUSION_CANCELLED. | |
| 1050 | + | // The server then annotates the job with GitHub's message "The runner has | |
| 1051 | + | // received a shutdown signal. This can happen when the runner service is | |
| 1052 | + | // stopped, or a manually started runner is canceled." The conclusion | |
| 1053 | + | // CANCELLED for this case is to be verified against a recording of GitHub. | |
| 1054 | + | RunnerShutdown bool `protobuf:"varint,4,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"` | |
| 1055 | + | // Steps are the final steps, with the same meaning as UpdateJobRequest.steps, | |
| 1056 | + | // applied in the same atomic step as the conclusion. | |
| 1057 | + | Steps []*StepUpdate `protobuf:"bytes,5,rep,name=steps,proto3" json:"steps,omitempty"` | |
| 1058 | + | unknownFields protoimpl.UnknownFields | |
| 1059 | + | sizeCache protoimpl.SizeCache | |
| 1060 | + | } | |
| 1061 | + | ||
| 1062 | + | func (x *CompleteJobRequest) Reset() { | |
| 1063 | + | *x = CompleteJobRequest{} | |
| 1064 | + | mi := &file_ermite_runner_proto_msgTypes[8] | |
| 1065 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1066 | + | ms.StoreMessageInfo(mi) | |
| 1067 | + | } | |
| 1068 | + | ||
| 1069 | + | func (x *CompleteJobRequest) String() string { | |
| 1070 | + | return protoimpl.X.MessageStringOf(x) | |
| 1071 | + | } | |
| 1072 | + | ||
| 1073 | + | func (*CompleteJobRequest) ProtoMessage() {} | |
| 1074 | + | ||
| 1075 | + | func (x *CompleteJobRequest) ProtoReflect() protoreflect.Message { | |
| 1076 | + | mi := &file_ermite_runner_proto_msgTypes[8] | |
| 1077 | + | if x != nil { | |
| 1078 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1079 | + | if ms.LoadMessageInfo() == nil { | |
| 1080 | + | ms.StoreMessageInfo(mi) | |
| 1081 | + | } | |
| 1082 | + | return ms | |
| 1083 | + | } | |
| 1084 | + | return mi.MessageOf(x) | |
| 1085 | + | } | |
| 1086 | + | ||
| 1087 | + | // Deprecated: Use CompleteJobRequest.ProtoReflect.Descriptor instead. | |
| 1088 | + | func (*CompleteJobRequest) Descriptor() ([]byte, []int) { | |
| 1089 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{8} | |
| 1090 | + | } | |
| 1091 | + | ||
| 1092 | + | func (x *CompleteJobRequest) GetJobId() string { | |
| 1093 | + | if x != nil { | |
| 1094 | + | return x.JobId | |
| 1095 | + | } | |
| 1096 | + | return "" | |
| 1097 | + | } | |
| 1098 | + | ||
| 1099 | + | func (x *CompleteJobRequest) GetConclusion() CiConclusion { | |
| 1100 | + | if x != nil { | |
| 1101 | + | return x.Conclusion | |
| 1102 | + | } | |
| 1103 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |
| 1104 | + | } | |
| 1105 | + | ||
| 1106 | + | func (x *CompleteJobRequest) GetOutputs() map[string]string { | |
| 1107 | + | if x != nil { | |
| 1108 | + | return x.Outputs | |
| 1109 | + | } | |
| 1110 | + | return nil | |
| 1111 | + | } | |
| 1112 | + | ||
| 1113 | + | func (x *CompleteJobRequest) GetRunnerShutdown() bool { | |
| 1114 | + | if x != nil { | |
| 1115 | + | return x.RunnerShutdown | |
| 1116 | + | } | |
| 1117 | + | return false | |
| 1118 | + | } | |
| 1119 | + | ||
| 1120 | + | func (x *CompleteJobRequest) GetSteps() []*StepUpdate { | |
| 1121 | + | if x != nil { | |
| 1122 | + | return x.Steps | |
| 1123 | + | } | |
| 1124 | + | return nil | |
| 1125 | + | } | |
| 1126 | + | ||
| 1127 | + | // CompleteJobResponse is the stored result of a job. | |
| 1128 | + | type CompleteJobResponse struct { | |
| 1129 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1130 | + | // Conclusion is the conclusion of the job as the server stored it. It is the | |
| 1131 | + | // one of the request when applied is true. | |
| 1132 | + | Conclusion CiConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |
| 1133 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |
| 1134 | + | // there is none. | |
| 1135 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |
| 1136 | + | // Applied is true when this call ended the job, false when the job had | |
| 1137 | + | // already ended (a retry, or the server ended it first) and nothing changed. | |
| 1138 | + | Applied bool `protobuf:"varint,3,opt,name=applied,proto3" json:"applied,omitempty"` | |
| 1139 | + | unknownFields protoimpl.UnknownFields | |
| 1140 | + | sizeCache protoimpl.SizeCache | |
| 1141 | + | } | |
| 1142 | + | ||
| 1143 | + | func (x *CompleteJobResponse) Reset() { | |
| 1144 | + | *x = CompleteJobResponse{} | |
| 1145 | + | mi := &file_ermite_runner_proto_msgTypes[9] | |
| 1146 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1147 | + | ms.StoreMessageInfo(mi) | |
| 1148 | + | } | |
| 1149 | + | ||
| 1150 | + | func (x *CompleteJobResponse) String() string { | |
| 1151 | + | return protoimpl.X.MessageStringOf(x) | |
| 1152 | + | } | |
| 1153 | + | ||
| 1154 | + | func (*CompleteJobResponse) ProtoMessage() {} | |
| 1155 | + | ||
| 1156 | + | func (x *CompleteJobResponse) ProtoReflect() protoreflect.Message { | |
| 1157 | + | mi := &file_ermite_runner_proto_msgTypes[9] | |
| 1158 | + | if x != nil { | |
| 1159 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1160 | + | if ms.LoadMessageInfo() == nil { | |
| 1161 | + | ms.StoreMessageInfo(mi) | |
| 1162 | + | } | |
| 1163 | + | return ms | |
| 1164 | + | } | |
| 1165 | + | return mi.MessageOf(x) | |
| 1166 | + | } | |
| 1167 | + | ||
| 1168 | + | // Deprecated: Use CompleteJobResponse.ProtoReflect.Descriptor instead. | |
| 1169 | + | func (*CompleteJobResponse) Descriptor() ([]byte, []int) { | |
| 1170 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{9} | |
| 1171 | + | } | |
| 1172 | + | ||
| 1173 | + | func (x *CompleteJobResponse) GetConclusion() CiConclusion { | |
| 1174 | + | if x != nil { | |
| 1175 | + | return x.Conclusion | |
| 1176 | + | } | |
| 1177 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |
| 1178 | + | } | |
| 1179 | + | ||
| 1180 | + | func (x *CompleteJobResponse) GetError() string { | |
| 1181 | + | if x != nil { | |
| 1182 | + | return x.Error | |
| 1183 | + | } | |
| 1184 | + | return "" | |
| 1185 | + | } | |
| 1186 | + | ||
| 1187 | + | func (x *CompleteJobResponse) GetApplied() bool { | |
| 1188 | + | if x != nil { | |
| 1189 | + | return x.Applied | |
| 1190 | + | } | |
| 1191 | + | return false | |
| 1192 | + | } | |
| 1193 | + | ||
| 1194 | + | // JobEnded says that a job has ended. It is sent in WatchJob and attached, as | |
| 1195 | + | // an error detail, to the failed_precondition errors that RunnerService returns | |
| 1196 | + | // for a job that has ended. | |
| 1197 | + | type JobEnded struct { | |
| 1198 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1199 | + | // Conclusion is how the job ended. | |
| 1200 | + | Conclusion CiConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |
| 1201 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |
| 1202 | + | // there is none. | |
| 1203 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |
| 1204 | + | unknownFields protoimpl.UnknownFields | |
| 1205 | + | sizeCache protoimpl.SizeCache | |
| 1206 | + | } | |
| 1207 | + | ||
| 1208 | + | func (x *JobEnded) Reset() { | |
| 1209 | + | *x = JobEnded{} | |
| 1210 | + | mi := &file_ermite_runner_proto_msgTypes[10] | |
| 1211 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1212 | + | ms.StoreMessageInfo(mi) | |
| 1213 | + | } | |
| 1214 | + | ||
| 1215 | + | func (x *JobEnded) String() string { | |
| 1216 | + | return protoimpl.X.MessageStringOf(x) | |
| 1217 | + | } | |
| 1218 | + | ||
| 1219 | + | func (*JobEnded) ProtoMessage() {} | |
| 1220 | + | ||
| 1221 | + | func (x *JobEnded) ProtoReflect() protoreflect.Message { | |
| 1222 | + | mi := &file_ermite_runner_proto_msgTypes[10] | |
| 1223 | + | if x != nil { | |
| 1224 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1225 | + | if ms.LoadMessageInfo() == nil { | |
| 1226 | + | ms.StoreMessageInfo(mi) | |
| 1227 | + | } | |
| 1228 | + | return ms | |
| 1229 | + | } | |
| 1230 | + | return mi.MessageOf(x) | |
| 1231 | + | } | |
| 1232 | + | ||
| 1233 | + | // Deprecated: Use JobEnded.ProtoReflect.Descriptor instead. | |
| 1234 | + | func (*JobEnded) Descriptor() ([]byte, []int) { | |
| 1235 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{10} | |
| 1236 | + | } | |
| 1237 | + | ||
| 1238 | + | func (x *JobEnded) GetConclusion() CiConclusion { | |
| 1239 | + | if x != nil { | |
| 1240 | + | return x.Conclusion | |
| 1241 | + | } | |
| 1242 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |
| 1243 | + | } | |
| 1244 | + | ||
| 1245 | + | func (x *JobEnded) GetError() string { | |
| 1246 | + | if x != nil { | |
| 1247 | + | return x.Error | |
| 1248 | + | } | |
| 1249 | + | return "" | |
| 1250 | + | } | |
| 1251 | + | ||
| 1026 | 1252 | // AppendLogRequest carries log lines of one step. | |
| 1027 | 1253 | type AppendLogRequest struct { | |
| 1028 | 1254 | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1029 | 1255 | // JobId is the Job.job_id. It must be the job of the token. | |
| 1030 | 1256 | JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` | |
| 1031 | 1257 | // 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. | |
| 1258 | + | // UpdateJobRequest.steps), at least 1, and at least the step of the last | |
| 1259 | + | // stored line. A step that UpdateJob has not announced yet is created by the | |
| 1260 | + | // first lines. See the rules on AppendLog. | |
| 1034 | 1261 | Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"` | |
| 1035 | 1262 | // Offset is the number of the first line of the request in the log of the | |
| 1036 | 1263 | // job, counted from 0 across all steps. See the rules on AppendLog. | |
| @@ -1044,7 +1271,7 @@ type AppendLogRequest struct { | |||
| 1044 | 1271 | ||
| 1045 | 1272 | func (x *AppendLogRequest) Reset() { | |
| 1046 | 1273 | *x = AppendLogRequest{} | |
| 1047 | - | mi := &file_ermite_runner_proto_msgTypes[8] | |
| 1274 | + | mi := &file_ermite_runner_proto_msgTypes[11] | |
| 1048 | 1275 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1049 | 1276 | ms.StoreMessageInfo(mi) | |
| 1050 | 1277 | } | |
| @@ -1056,7 +1283,7 @@ func (x *AppendLogRequest) String() string { | |||
| 1056 | 1283 | func (*AppendLogRequest) ProtoMessage() {} | |
| 1057 | 1284 | ||
| 1058 | 1285 | func (x *AppendLogRequest) ProtoReflect() protoreflect.Message { | |
| 1059 | - | mi := &file_ermite_runner_proto_msgTypes[8] | |
| 1286 | + | mi := &file_ermite_runner_proto_msgTypes[11] | |
| 1060 | 1287 | if x != nil { | |
| 1061 | 1288 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1062 | 1289 | if ms.LoadMessageInfo() == nil { | |
| @@ -1069,7 +1296,7 @@ func (x *AppendLogRequest) ProtoReflect() protoreflect.Message { | |||
| 1069 | 1296 | ||
| 1070 | 1297 | // Deprecated: Use AppendLogRequest.ProtoReflect.Descriptor instead. | |
| 1071 | 1298 | func (*AppendLogRequest) Descriptor() ([]byte, []int) { | |
| 1072 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{8} | |
| 1299 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{11} | |
| 1073 | 1300 | } | |
| 1074 | 1301 | ||
| 1075 | 1302 | func (x *AppendLogRequest) GetJobId() string { | |
| @@ -1110,14 +1337,23 @@ type LogLine struct { | |||
| 1110 | 1337 | // replaces invalid bytes with U+FFFD). It must not contain a line feed; a | |
| 1111 | 1338 | // carriage return is kept. It is the line as GitHub's raw log has it, | |
| 1112 | 1339 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | |
| 1113 | - | Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` | |
| 1340 | + | Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` | |
| 1341 | + | // Continued is true when this line is the continuation of the line before | |
| 1342 | + | // it, which was too long to send whole. A runner cuts a line of more than | |
| 1343 | + | // 64 KiB into pieces at UTF-8 character boundaries, each piece of at most | |
| 1344 | + | // 64 KiB, and sets continued on every piece but the first. Each piece is one | |
| 1345 | + | // line for numbering and offsets; the server and the raw log join the | |
| 1346 | + | // pieces back, so the original line is rebuilt exactly. The first line of a | |
| 1347 | + | // job cannot be continued (invalid_argument) and a continued line is in | |
| 1348 | + | // the same step as the line before it (failed_precondition otherwise). | |
| 1349 | + | Continued bool `protobuf:"varint,3,opt,name=continued,proto3" json:"continued,omitempty"` | |
| 1114 | 1350 | unknownFields protoimpl.UnknownFields | |
| 1115 | 1351 | sizeCache protoimpl.SizeCache | |
| 1116 | 1352 | } | |
| 1117 | 1353 | ||
| 1118 | 1354 | func (x *LogLine) Reset() { | |
| 1119 | 1355 | *x = LogLine{} | |
| 1120 | - | mi := &file_ermite_runner_proto_msgTypes[9] | |
| 1356 | + | mi := &file_ermite_runner_proto_msgTypes[12] | |
| 1121 | 1357 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1122 | 1358 | ms.StoreMessageInfo(mi) | |
| 1123 | 1359 | } | |
| @@ -1129,7 +1365,7 @@ func (x *LogLine) String() string { | |||
| 1129 | 1365 | func (*LogLine) ProtoMessage() {} | |
| 1130 | 1366 | ||
| 1131 | 1367 | func (x *LogLine) ProtoReflect() protoreflect.Message { | |
| 1132 | - | mi := &file_ermite_runner_proto_msgTypes[9] | |
| 1368 | + | mi := &file_ermite_runner_proto_msgTypes[12] | |
| 1133 | 1369 | if x != nil { | |
| 1134 | 1370 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1135 | 1371 | if ms.LoadMessageInfo() == nil { | |
| @@ -1142,7 +1378,7 @@ func (x *LogLine) ProtoReflect() protoreflect.Message { | |||
| 1142 | 1378 | ||
| 1143 | 1379 | // Deprecated: Use LogLine.ProtoReflect.Descriptor instead. | |
| 1144 | 1380 | func (*LogLine) Descriptor() ([]byte, []int) { | |
| 1145 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{9} | |
| 1381 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{12} | |
| 1146 | 1382 | } | |
| 1147 | 1383 | ||
| 1148 | 1384 | func (x *LogLine) GetTime() *timestamppb.Timestamp { | |
| @@ -1159,6 +1395,13 @@ func (x *LogLine) GetText() string { | |||
| 1159 | 1395 | return "" | |
| 1160 | 1396 | } | |
| 1161 | 1397 | ||
| 1398 | + | func (x *LogLine) GetContinued() bool { | |
| 1399 | + | if x != nil { | |
| 1400 | + | return x.Continued | |
| 1401 | + | } | |
| 1402 | + | return false | |
| 1403 | + | } | |
| 1404 | + | ||
| 1162 | 1405 | // AppendLogResponse tells how much of the log the server holds. | |
| 1163 | 1406 | type AppendLogResponse struct { | |
| 1164 | 1407 | state protoimpl.MessageState `protogen:"open.v1"` | |
| @@ -1171,7 +1414,7 @@ type AppendLogResponse struct { | |||
| 1171 | 1414 | ||
| 1172 | 1415 | func (x *AppendLogResponse) Reset() { | |
| 1173 | 1416 | *x = AppendLogResponse{} | |
| 1174 | - | mi := &file_ermite_runner_proto_msgTypes[10] | |
| 1417 | + | mi := &file_ermite_runner_proto_msgTypes[13] | |
| 1175 | 1418 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1176 | 1419 | ms.StoreMessageInfo(mi) | |
| 1177 | 1420 | } | |
| @@ -1183,7 +1426,7 @@ func (x *AppendLogResponse) String() string { | |||
| 1183 | 1426 | func (*AppendLogResponse) ProtoMessage() {} | |
| 1184 | 1427 | ||
| 1185 | 1428 | func (x *AppendLogResponse) ProtoReflect() protoreflect.Message { | |
| 1186 | - | mi := &file_ermite_runner_proto_msgTypes[10] | |
| 1429 | + | mi := &file_ermite_runner_proto_msgTypes[13] | |
| 1187 | 1430 | if x != nil { | |
| 1188 | 1431 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1189 | 1432 | if ms.LoadMessageInfo() == nil { | |
| @@ -1196,7 +1439,7 @@ func (x *AppendLogResponse) ProtoReflect() protoreflect.Message { | |||
| 1196 | 1439 | ||
| 1197 | 1440 | // Deprecated: Use AppendLogResponse.ProtoReflect.Descriptor instead. | |
| 1198 | 1441 | func (*AppendLogResponse) Descriptor() ([]byte, []int) { | |
| 1199 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{10} | |
| 1442 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{13} | |
| 1200 | 1443 | } | |
| 1201 | 1444 | ||
| 1202 | 1445 | func (x *AppendLogResponse) GetNextOffset() int64 { | |
| @@ -1217,7 +1460,7 @@ type HeartbeatRequest struct { | |||
| 1217 | 1460 | ||
| 1218 | 1461 | func (x *HeartbeatRequest) Reset() { | |
| 1219 | 1462 | *x = HeartbeatRequest{} | |
| 1220 | - | mi := &file_ermite_runner_proto_msgTypes[11] | |
| 1463 | + | mi := &file_ermite_runner_proto_msgTypes[14] | |
| 1221 | 1464 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1222 | 1465 | ms.StoreMessageInfo(mi) | |
| 1223 | 1466 | } | |
| @@ -1229,7 +1472,7 @@ func (x *HeartbeatRequest) String() string { | |||
| 1229 | 1472 | func (*HeartbeatRequest) ProtoMessage() {} | |
| 1230 | 1473 | ||
| 1231 | 1474 | func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { | |
| 1232 | - | mi := &file_ermite_runner_proto_msgTypes[11] | |
| 1475 | + | mi := &file_ermite_runner_proto_msgTypes[14] | |
| 1233 | 1476 | if x != nil { | |
| 1234 | 1477 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1235 | 1478 | if ms.LoadMessageInfo() == nil { | |
| @@ -1242,7 +1485,7 @@ func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { | |||
| 1242 | 1485 | ||
| 1243 | 1486 | // Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. | |
| 1244 | 1487 | func (*HeartbeatRequest) Descriptor() ([]byte, []int) { | |
| 1245 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{11} | |
| 1488 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{14} | |
| 1246 | 1489 | } | |
| 1247 | 1490 | ||
| 1248 | 1491 | func (x *HeartbeatRequest) GetJobId() string { | |
| @@ -1261,7 +1504,7 @@ type HeartbeatResponse struct { | |||
| 1261 | 1504 | ||
| 1262 | 1505 | func (x *HeartbeatResponse) Reset() { | |
| 1263 | 1506 | *x = HeartbeatResponse{} | |
| 1264 | - | mi := &file_ermite_runner_proto_msgTypes[12] | |
| 1507 | + | mi := &file_ermite_runner_proto_msgTypes[15] | |
| 1265 | 1508 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1266 | 1509 | ms.StoreMessageInfo(mi) | |
| 1267 | 1510 | } | |
| @@ -1273,7 +1516,7 @@ func (x *HeartbeatResponse) String() string { | |||
| 1273 | 1516 | func (*HeartbeatResponse) ProtoMessage() {} | |
| 1274 | 1517 | ||
| 1275 | 1518 | func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { | |
| 1276 | - | mi := &file_ermite_runner_proto_msgTypes[12] | |
| 1519 | + | mi := &file_ermite_runner_proto_msgTypes[15] | |
| 1277 | 1520 | if x != nil { | |
| 1278 | 1521 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1279 | 1522 | if ms.LoadMessageInfo() == nil { | |
| @@ -1286,7 +1529,7 @@ func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { | |||
| 1286 | 1529 | ||
| 1287 | 1530 | // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. | |
| 1288 | 1531 | func (*HeartbeatResponse) Descriptor() ([]byte, []int) { | |
| 1289 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{12} | |
| 1532 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{15} | |
| 1290 | 1533 | } | |
| 1291 | 1534 | ||
| 1292 | 1535 | // WatchJobRequest names the job to watch. | |
| @@ -1300,7 +1543,7 @@ type WatchJobRequest struct { | |||
| 1300 | 1543 | ||
| 1301 | 1544 | func (x *WatchJobRequest) Reset() { | |
| 1302 | 1545 | *x = WatchJobRequest{} | |
| 1303 | - | mi := &file_ermite_runner_proto_msgTypes[13] | |
| 1546 | + | mi := &file_ermite_runner_proto_msgTypes[16] | |
| 1304 | 1547 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1305 | 1548 | ms.StoreMessageInfo(mi) | |
| 1306 | 1549 | } | |
| @@ -1312,7 +1555,7 @@ func (x *WatchJobRequest) String() string { | |||
| 1312 | 1555 | func (*WatchJobRequest) ProtoMessage() {} | |
| 1313 | 1556 | ||
| 1314 | 1557 | func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { | |
| 1315 | - | mi := &file_ermite_runner_proto_msgTypes[13] | |
| 1558 | + | mi := &file_ermite_runner_proto_msgTypes[16] | |
| 1316 | 1559 | if x != nil { | |
| 1317 | 1560 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1318 | 1561 | if ms.LoadMessageInfo() == nil { | |
| @@ -1325,7 +1568,7 @@ func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { | |||
| 1325 | 1568 | ||
| 1326 | 1569 | // Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead. | |
| 1327 | 1570 | func (*WatchJobRequest) Descriptor() ([]byte, []int) { | |
| 1328 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{13} | |
| 1571 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{16} | |
| 1329 | 1572 | } | |
| 1330 | 1573 | ||
| 1331 | 1574 | func (x *WatchJobRequest) GetJobId() string { | |
| @@ -1335,28 +1578,25 @@ func (x *WatchJobRequest) GetJobId() string { | |||
| 1335 | 1578 | return "" | |
| 1336 | 1579 | } | |
| 1337 | 1580 | ||
| 1338 | - | // WatchJobResponse is one message of the stream: a request to cancel the job, | |
| 1339 | - | // or a keep-alive. | |
| 1581 | + | // WatchJobResponse is one message of the stream. | |
| 1340 | 1582 | type WatchJobResponse struct { | |
| 1341 | 1583 | 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 CI_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"` | |
| 1584 | + | // Msg is what the message carries. New kinds may be added: a client ignores | |
| 1585 | + | // the ones it does not know. | |
| 1586 | + | // | |
| 1587 | + | // Types that are valid to be assigned to Msg: | |
| 1588 | + | // | |
| 1589 | + | // *WatchJobResponse_Keepalive | |
| 1590 | + | // *WatchJobResponse_Cancel | |
| 1591 | + | // *WatchJobResponse_Ended | |
| 1592 | + | Msg isWatchJobResponse_Msg `protobuf_oneof:"msg"` | |
| 1353 | 1593 | unknownFields protoimpl.UnknownFields | |
| 1354 | 1594 | sizeCache protoimpl.SizeCache | |
| 1355 | 1595 | } | |
| 1356 | 1596 | ||
| 1357 | 1597 | func (x *WatchJobResponse) Reset() { | |
| 1358 | 1598 | *x = WatchJobResponse{} | |
| 1359 | - | mi := &file_ermite_runner_proto_msgTypes[14] | |
| 1599 | + | mi := &file_ermite_runner_proto_msgTypes[17] | |
| 1360 | 1600 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1361 | 1601 | ms.StoreMessageInfo(mi) | |
| 1362 | 1602 | } | |
| @@ -1368,7 +1608,7 @@ func (x *WatchJobResponse) String() string { | |||
| 1368 | 1608 | func (*WatchJobResponse) ProtoMessage() {} | |
| 1369 | 1609 | ||
| 1370 | 1610 | func (x *WatchJobResponse) ProtoReflect() protoreflect.Message { | |
| 1371 | - | mi := &file_ermite_runner_proto_msgTypes[14] | |
| 1611 | + | mi := &file_ermite_runner_proto_msgTypes[17] | |
| 1372 | 1612 | if x != nil { | |
| 1373 | 1613 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1374 | 1614 | if ms.LoadMessageInfo() == nil { | |
| @@ -1381,24 +1621,167 @@ func (x *WatchJobResponse) ProtoReflect() protoreflect.Message { | |||
| 1381 | 1621 | ||
| 1382 | 1622 | // Deprecated: Use WatchJobResponse.ProtoReflect.Descriptor instead. | |
| 1383 | 1623 | func (*WatchJobResponse) Descriptor() ([]byte, []int) { | |
| 1384 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{14} | |
| 1624 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{17} | |
| 1385 | 1625 | } | |
| 1386 | 1626 | ||
| 1387 | - | func (x *WatchJobResponse) GetReason() CancelReason { | |
| 1627 | + | func (x *WatchJobResponse) GetMsg() isWatchJobResponse_Msg { | |
| 1628 | + | if x != nil { | |
| 1629 | + | return x.Msg | |
| 1630 | + | } | |
| 1631 | + | return nil | |
| 1632 | + | } | |
| 1633 | + | ||
| 1634 | + | func (x *WatchJobResponse) GetKeepalive() *Keepalive { | |
| 1635 | + | if x != nil { | |
| 1636 | + | if x, ok := x.Msg.(*WatchJobResponse_Keepalive); ok { | |
| 1637 | + | return x.Keepalive | |
| 1638 | + | } | |
| 1639 | + | } | |
| 1640 | + | return nil | |
| 1641 | + | } | |
| 1642 | + | ||
| 1643 | + | func (x *WatchJobResponse) GetCancel() *CancelRequest { | |
| 1644 | + | if x != nil { | |
| 1645 | + | if x, ok := x.Msg.(*WatchJobResponse_Cancel); ok { | |
| 1646 | + | return x.Cancel | |
| 1647 | + | } | |
| 1648 | + | } | |
| 1649 | + | return nil | |
| 1650 | + | } | |
| 1651 | + | ||
| 1652 | + | func (x *WatchJobResponse) GetEnded() *JobEnded { | |
| 1653 | + | if x != nil { | |
| 1654 | + | if x, ok := x.Msg.(*WatchJobResponse_Ended); ok { | |
| 1655 | + | return x.Ended | |
| 1656 | + | } | |
| 1657 | + | } | |
| 1658 | + | return nil | |
| 1659 | + | } | |
| 1660 | + | ||
| 1661 | + | type isWatchJobResponse_Msg interface { | |
| 1662 | + | isWatchJobResponse_Msg() | |
| 1663 | + | } | |
| 1664 | + | ||
| 1665 | + | type WatchJobResponse_Keepalive struct { | |
| 1666 | + | // Keepalive: nothing to do, the stream is alive. | |
| 1667 | + | Keepalive *Keepalive `protobuf:"bytes,1,opt,name=keepalive,proto3,oneof"` | |
| 1668 | + | } | |
| 1669 | + | ||
| 1670 | + | type WatchJobResponse_Cancel struct { | |
| 1671 | + | // Cancel: the job must stop. | |
| 1672 | + | Cancel *CancelRequest `protobuf:"bytes,2,opt,name=cancel,proto3,oneof"` | |
| 1673 | + | } | |
| 1674 | + | ||
| 1675 | + | type WatchJobResponse_Ended struct { | |
| 1676 | + | // Ended: the job has ended. The last message of a stream: a close without | |
| 1677 | + | // it means the client reconnects. | |
| 1678 | + | Ended *JobEnded `protobuf:"bytes,3,opt,name=ended,proto3,oneof"` | |
| 1679 | + | } | |
| 1680 | + | ||
| 1681 | + | func (*WatchJobResponse_Keepalive) isWatchJobResponse_Msg() {} | |
| 1682 | + | ||
| 1683 | + | func (*WatchJobResponse_Cancel) isWatchJobResponse_Msg() {} | |
| 1684 | + | ||
| 1685 | + | func (*WatchJobResponse_Ended) isWatchJobResponse_Msg() {} | |
| 1686 | + | ||
| 1687 | + | // Keepalive has no fields. | |
| 1688 | + | type Keepalive struct { | |
| 1689 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1690 | + | unknownFields protoimpl.UnknownFields | |
| 1691 | + | sizeCache protoimpl.SizeCache | |
| 1692 | + | } | |
| 1693 | + | ||
| 1694 | + | func (x *Keepalive) Reset() { | |
| 1695 | + | *x = Keepalive{} | |
| 1696 | + | mi := &file_ermite_runner_proto_msgTypes[18] | |
| 1697 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1698 | + | ms.StoreMessageInfo(mi) | |
| 1699 | + | } | |
| 1700 | + | ||
| 1701 | + | func (x *Keepalive) String() string { | |
| 1702 | + | return protoimpl.X.MessageStringOf(x) | |
| 1703 | + | } | |
| 1704 | + | ||
| 1705 | + | func (*Keepalive) ProtoMessage() {} | |
| 1706 | + | ||
| 1707 | + | func (x *Keepalive) ProtoReflect() protoreflect.Message { | |
| 1708 | + | mi := &file_ermite_runner_proto_msgTypes[18] | |
| 1709 | + | if x != nil { | |
| 1710 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1711 | + | if ms.LoadMessageInfo() == nil { | |
| 1712 | + | ms.StoreMessageInfo(mi) | |
| 1713 | + | } | |
| 1714 | + | return ms | |
| 1715 | + | } | |
| 1716 | + | return mi.MessageOf(x) | |
| 1717 | + | } | |
| 1718 | + | ||
| 1719 | + | // Deprecated: Use Keepalive.ProtoReflect.Descriptor instead. | |
| 1720 | + | func (*Keepalive) Descriptor() ([]byte, []int) { | |
| 1721 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{18} | |
| 1722 | + | } | |
| 1723 | + | ||
| 1724 | + | // CancelRequest asks the runner to stop a job. | |
| 1725 | + | type CancelRequest struct { | |
| 1726 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1727 | + | // Reason is why the job must stop. New reasons may be added; a runner that | |
| 1728 | + | // does not know one cancels the job and reports CI_CONCLUSION_CANCELLED. | |
| 1729 | + | Reason CancelReason `protobuf:"varint,1,opt,name=reason,proto3,enum=ermite.CancelReason" json:"reason,omitempty"` | |
| 1730 | + | // Message is a short human-readable explanation, for the job log, for | |
| 1731 | + | // example "Canceled by the owner". At most 1,024 characters. | |
| 1732 | + | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` | |
| 1733 | + | // RequestedAt is when the cancellation was requested, by the clock of the | |
| 1734 | + | // server. | |
| 1735 | + | RequestedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=requested_at,json=requestedAt,proto3" json:"requested_at,omitempty"` | |
| 1736 | + | unknownFields protoimpl.UnknownFields | |
| 1737 | + | sizeCache protoimpl.SizeCache | |
| 1738 | + | } | |
| 1739 | + | ||
| 1740 | + | func (x *CancelRequest) Reset() { | |
| 1741 | + | *x = CancelRequest{} | |
| 1742 | + | mi := &file_ermite_runner_proto_msgTypes[19] | |
| 1743 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1744 | + | ms.StoreMessageInfo(mi) | |
| 1745 | + | } | |
| 1746 | + | ||
| 1747 | + | func (x *CancelRequest) String() string { | |
| 1748 | + | return protoimpl.X.MessageStringOf(x) | |
| 1749 | + | } | |
| 1750 | + | ||
| 1751 | + | func (*CancelRequest) ProtoMessage() {} | |
| 1752 | + | ||
| 1753 | + | func (x *CancelRequest) ProtoReflect() protoreflect.Message { | |
| 1754 | + | mi := &file_ermite_runner_proto_msgTypes[19] | |
| 1755 | + | if x != nil { | |
| 1756 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1757 | + | if ms.LoadMessageInfo() == nil { | |
| 1758 | + | ms.StoreMessageInfo(mi) | |
| 1759 | + | } | |
| 1760 | + | return ms | |
| 1761 | + | } | |
| 1762 | + | return mi.MessageOf(x) | |
| 1763 | + | } | |
| 1764 | + | ||
| 1765 | + | // Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead. | |
| 1766 | + | func (*CancelRequest) Descriptor() ([]byte, []int) { | |
| 1767 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{19} | |
| 1768 | + | } | |
| 1769 | + | ||
| 1770 | + | func (x *CancelRequest) GetReason() CancelReason { | |
| 1388 | 1771 | if x != nil { | |
| 1389 | 1772 | return x.Reason | |
| 1390 | 1773 | } | |
| 1391 | 1774 | return CancelReason_CANCEL_REASON_UNSPECIFIED | |
| 1392 | 1775 | } | |
| 1393 | 1776 | ||
| 1394 | - | func (x *WatchJobResponse) GetMessage() string { | |
| 1777 | + | func (x *CancelRequest) GetMessage() string { | |
| 1395 | 1778 | if x != nil { | |
| 1396 | 1779 | return x.Message | |
| 1397 | 1780 | } | |
| 1398 | 1781 | return "" | |
| 1399 | 1782 | } | |
| 1400 | 1783 | ||
| 1401 | - | func (x *WatchJobResponse) GetRequestedAt() *timestamppb.Timestamp { | |
| 1784 | + | func (x *CancelRequest) GetRequestedAt() *timestamppb.Timestamp { | |
| 1402 | 1785 | if x != nil { | |
| 1403 | 1786 | return x.RequestedAt | |
| 1404 | 1787 | } | |
| @@ -1409,21 +1792,25 @@ var File_ermite_runner_proto protoreflect.FileDescriptor | |||
| 1409 | 1792 | ||
| 1410 | 1793 | const file_ermite_runner_proto_rawDesc = "" + | |
| 1411 | 1794 | "\n" + | |
| 1412 | - | "\x13ermite/runner.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb8\x01\n" + | |
| 1795 | + | "\x13ermite/runner.proto\x12\x06ermite\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd2\x01\n" + | |
| 1413 | 1796 | "\x15RegisterRunnerRequest\x12\x12\n" + | |
| 1414 | 1797 | "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + | |
| 1415 | 1798 | "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x18\n" + | |
| 1416 | 1799 | "\aversion\x18\x03 \x01(\tR\aversion\x125\n" + | |
| 1417 | 1800 | "\tisolation\x18\x04 \x01(\x0e2\x17.ermite.RunnerIsolationR\tisolation\x12\x12\n" + | |
| 1418 | 1801 | "\x04arch\x18\x05 \x01(\tR\x04arch\x12\x0e\n" + | |
| 1419 | - | "\x02os\x18\x06 \x01(\tR\x02os\"X\n" + | |
| 1802 | + | "\x02os\x18\x06 \x01(\tR\x02os\x12\x18\n" + | |
| 1803 | + | "\areplace\x18\a \x01(\bR\areplace\"X\n" + | |
| 1420 | 1804 | "\x16RegisterRunnerResponse\x12\x1b\n" + | |
| 1421 | 1805 | "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" + | |
| 1422 | - | "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\")\n" + | |
| 1806 | + | "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\"B\n" + | |
| 1423 | 1807 | "\x0fFetchJobRequest\x12\x16\n" + | |
| 1424 | - | "\x06labels\x18\x01 \x03(\tR\x06labels\"1\n" + | |
| 1808 | + | "\x06labels\x18\x01 \x03(\tR\x06labels\x12\x17\n" + | |
| 1809 | + | "\apoll_id\x18\x02 \x01(\tR\x06pollId\"m\n" + | |
| 1425 | 1810 | "\x10FetchJobResponse\x12\x1d\n" + | |
| 1426 | - | "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\"\xfa\x02\n" + | |
| 1811 | + | "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\x12:\n" + | |
| 1812 | + | "\vretry_after\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\n" + | |
| 1813 | + | "retryAfter\"\xfa\x02\n" + | |
| 1427 | 1814 | "\x03Job\x12\x15\n" + | |
| 1428 | 1815 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" + | |
| 1429 | 1816 | "\x06run_id\x18\x02 \x01(\x03R\x05runId\x12\x18\n" + | |
| @@ -1439,18 +1826,10 @@ const file_ermite_runner_proto_rawDesc = "" + | |||
| 1439 | 1826 | "\x0ftimeout_minutes\x18\v \x01(\x05R\x0etimeoutMinutes\x12\x12\n" + | |
| 1440 | 1827 | "\x04repo\x18\f \x01(\tR\x04repo\x12\x10\n" + | |
| 1441 | 1828 | "\x03sha\x18\r \x01(\tR\x03sha\x12\x10\n" + | |
| 1442 | - | "\x03ref\x18\x0e \x01(\tR\x03ref\"\xaf\x02\n" + | |
| 1829 | + | "\x03ref\x18\x0e \x01(\tR\x03ref\"S\n" + | |
| 1443 | 1830 | "\x10UpdateJobRequest\x12\x15\n" + | |
| 1444 | 1831 | "\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\x124\n" + | |
| 1447 | - | "\n" + | |
| 1448 | - | "conclusion\x18\x04 \x01(\x0e2\x14.ermite.CiConclusionR\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\"\x91\x02\n" + | |
| 1832 | + | "\x05steps\x18\x02 \x03(\v2\x12.ermite.StepUpdateR\x05steps\"\x91\x02\n" + | |
| 1454 | 1833 | "\n" + | |
| 1455 | 1834 | "StepUpdate\x12\x16\n" + | |
| 1456 | 1835 | "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" + | |
| @@ -1462,15 +1841,38 @@ const file_ermite_runner_proto_rawDesc = "" + | |||
| 1462 | 1841 | "\n" + | |
| 1463 | 1842 | "started_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" + | |
| 1464 | 1843 | "\fcompleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\"\x13\n" + | |
| 1465 | - | "\x11UpdateJobResponse\"|\n" + | |
| 1844 | + | "\x11UpdateJobResponse\"\xb3\x02\n" + | |
| 1845 | + | "\x12CompleteJobRequest\x12\x15\n" + | |
| 1846 | + | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x124\n" + | |
| 1847 | + | "\n" + | |
| 1848 | + | "conclusion\x18\x02 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |
| 1849 | + | "conclusion\x12A\n" + | |
| 1850 | + | "\aoutputs\x18\x03 \x03(\v2'.ermite.CompleteJobRequest.OutputsEntryR\aoutputs\x12'\n" + | |
| 1851 | + | "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x12(\n" + | |
| 1852 | + | "\x05steps\x18\x05 \x03(\v2\x12.ermite.StepUpdateR\x05steps\x1a:\n" + | |
| 1853 | + | "\fOutputsEntry\x12\x10\n" + | |
| 1854 | + | "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + | |
| 1855 | + | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"{\n" + | |
| 1856 | + | "\x13CompleteJobResponse\x124\n" + | |
| 1857 | + | "\n" + | |
| 1858 | + | "conclusion\x18\x01 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |
| 1859 | + | "conclusion\x12\x14\n" + | |
| 1860 | + | "\x05error\x18\x02 \x01(\tR\x05error\x12\x18\n" + | |
| 1861 | + | "\aapplied\x18\x03 \x01(\bR\aapplied\"V\n" + | |
| 1862 | + | "\bJobEnded\x124\n" + | |
| 1863 | + | "\n" + | |
| 1864 | + | "conclusion\x18\x01 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |
| 1865 | + | "conclusion\x12\x14\n" + | |
| 1866 | + | "\x05error\x18\x02 \x01(\tR\x05error\"|\n" + | |
| 1466 | 1867 | "\x10AppendLogRequest\x12\x15\n" + | |
| 1467 | 1868 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x12\n" + | |
| 1468 | 1869 | "\x04step\x18\x02 \x01(\x05R\x04step\x12\x16\n" + | |
| 1469 | 1870 | "\x06offset\x18\x03 \x01(\x03R\x06offset\x12%\n" + | |
| 1470 | - | "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"M\n" + | |
| 1871 | + | "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"k\n" + | |
| 1471 | 1872 | "\aLogLine\x12.\n" + | |
| 1472 | 1873 | "\x04time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x12\x12\n" + | |
| 1473 | - | "\x04text\x18\x02 \x01(\tR\x04text\"4\n" + | |
| 1874 | + | "\x04text\x18\x02 \x01(\tR\x04text\x12\x1c\n" + | |
| 1875 | + | "\tcontinued\x18\x03 \x01(\bR\tcontinued\"4\n" + | |
| 1474 | 1876 | "\x11AppendLogResponse\x12\x1f\n" + | |
| 1475 | 1877 | "\vnext_offset\x18\x01 \x01(\x03R\n" + | |
| 1476 | 1878 | "nextOffset\")\n" + | |
| @@ -1478,8 +1880,14 @@ const file_ermite_runner_proto_rawDesc = "" + | |||
| 1478 | 1880 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x13\n" + | |
| 1479 | 1881 | "\x11HeartbeatResponse\"(\n" + | |
| 1480 | 1882 | "\x0fWatchJobRequest\x12\x15\n" + | |
| 1481 | - | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x99\x01\n" + | |
| 1482 | - | "\x10WatchJobResponse\x12,\n" + | |
| 1883 | + | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\xa7\x01\n" + | |
| 1884 | + | "\x10WatchJobResponse\x121\n" + | |
| 1885 | + | "\tkeepalive\x18\x01 \x01(\v2\x11.ermite.KeepaliveH\x00R\tkeepalive\x12/\n" + | |
| 1886 | + | "\x06cancel\x18\x02 \x01(\v2\x15.ermite.CancelRequestH\x00R\x06cancel\x12(\n" + | |
| 1887 | + | "\x05ended\x18\x03 \x01(\v2\x10.ermite.JobEndedH\x00R\x05endedB\x05\n" + | |
| 1888 | + | "\x03msg\"\v\n" + | |
| 1889 | + | "\tKeepalive\"\x96\x01\n" + | |
| 1890 | + | "\rCancelRequest\x12,\n" + | |
| 1483 | 1891 | "\x06reason\x18\x01 \x01(\x0e2\x14.ermite.CancelReasonR\x06reason\x12\x18\n" + | |
| 1484 | 1892 | "\amessage\x18\x02 \x01(\tR\amessage\x12=\n" + | |
| 1485 | 1893 | "\frequested_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt*o\n" + | |
| @@ -1511,14 +1919,15 @@ const file_ermite_runner_proto_rawDesc = "" + | |||
| 1511 | 1919 | "\x19CANCEL_REASON_UNSPECIFIED\x10\x00\x12\x16\n" + | |
| 1512 | 1920 | "\x12CANCEL_REASON_USER\x10\x01\x12\x1b\n" + | |
| 1513 | 1921 | "\x17CANCEL_REASON_FAIL_FAST\x10\x02\x12\x19\n" + | |
| 1514 | - | "\x15CANCEL_REASON_TIMEOUT\x10\x032\xa6\x03\n" + | |
| 1922 | + | "\x15CANCEL_REASON_TIMEOUT\x10\x032\xee\x03\n" + | |
| 1515 | 1923 | "\rRunnerService\x12O\n" + | |
| 1516 | 1924 | "\x0eRegisterRunner\x12\x1d.ermite.RegisterRunnerRequest\x1a\x1e.ermite.RegisterRunnerResponse\x12=\n" + | |
| 1517 | 1925 | "\bFetchJob\x12\x17.ermite.FetchJobRequest\x1a\x18.ermite.FetchJobResponse\x12@\n" + | |
| 1518 | 1926 | "\tUpdateJob\x12\x18.ermite.UpdateJobRequest\x1a\x19.ermite.UpdateJobResponse\x12@\n" + | |
| 1519 | 1927 | "\tAppendLog\x12\x18.ermite.AppendLogRequest\x1a\x19.ermite.AppendLogResponse\x12@\n" + | |
| 1520 | 1928 | "\tHeartbeat\x12\x18.ermite.HeartbeatRequest\x1a\x19.ermite.HeartbeatResponse\x12?\n" + | |
| 1521 | - | "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01Br\n" + | |
| 1929 | + | "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01\x12F\n" + | |
| 1930 | + | "\vCompleteJob\x12\x1a.ermite.CompleteJobRequest\x1a\x1b.ermite.CompleteJobResponseBr\n" + | |
| 1522 | 1931 | "\n" + | |
| 1523 | 1932 | "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" | |
| 1524 | 1933 | ||
| @@ -1535,7 +1944,7 @@ func file_ermite_runner_proto_rawDescGZIP() []byte { | |||
| 1535 | 1944 | } | |
| 1536 | 1945 | ||
| 1537 | 1946 | var file_ermite_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 5) | |
| 1538 | - | var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 16) | |
| 1947 | + | var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 21) | |
| 1539 | 1948 | var file_ermite_runner_proto_goTypes = []any{ | |
| 1540 | 1949 | (CiStatus)(0), // 0: ermite.CiStatus | |
| 1541 | 1950 | (CiConclusion)(0), // 1: ermite.CiConclusion | |
| @@ -1550,47 +1959,62 @@ var file_ermite_runner_proto_goTypes = []any{ | |||
| 1550 | 1959 | (*UpdateJobRequest)(nil), // 10: ermite.UpdateJobRequest | |
| 1551 | 1960 | (*StepUpdate)(nil), // 11: ermite.StepUpdate | |
| 1552 | 1961 | (*UpdateJobResponse)(nil), // 12: ermite.UpdateJobResponse | |
| 1553 | - | (*AppendLogRequest)(nil), // 13: ermite.AppendLogRequest | |
| 1554 | - | (*LogLine)(nil), // 14: ermite.LogLine | |
| 1555 | - | (*AppendLogResponse)(nil), // 15: ermite.AppendLogResponse | |
| 1556 | - | (*HeartbeatRequest)(nil), // 16: ermite.HeartbeatRequest | |
| 1557 | - | (*HeartbeatResponse)(nil), // 17: ermite.HeartbeatResponse | |
| 1558 | - | (*WatchJobRequest)(nil), // 18: ermite.WatchJobRequest | |
| 1559 | - | (*WatchJobResponse)(nil), // 19: ermite.WatchJobResponse | |
| 1560 | - | nil, // 20: ermite.UpdateJobRequest.OutputsEntry | |
| 1561 | - | (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp | |
| 1962 | + | (*CompleteJobRequest)(nil), // 13: ermite.CompleteJobRequest | |
| 1963 | + | (*CompleteJobResponse)(nil), // 14: ermite.CompleteJobResponse | |
| 1964 | + | (*JobEnded)(nil), // 15: ermite.JobEnded | |
| 1965 | + | (*AppendLogRequest)(nil), // 16: ermite.AppendLogRequest | |
| 1966 | + | (*LogLine)(nil), // 17: ermite.LogLine | |
| 1967 | + | (*AppendLogResponse)(nil), // 18: ermite.AppendLogResponse | |
| 1968 | + | (*HeartbeatRequest)(nil), // 19: ermite.HeartbeatRequest | |
| 1969 | + | (*HeartbeatResponse)(nil), // 20: ermite.HeartbeatResponse | |
| 1970 | + | (*WatchJobRequest)(nil), // 21: ermite.WatchJobRequest | |
| 1971 | + | (*WatchJobResponse)(nil), // 22: ermite.WatchJobResponse | |
| 1972 | + | (*Keepalive)(nil), // 23: ermite.Keepalive | |
| 1973 | + | (*CancelRequest)(nil), // 24: ermite.CancelRequest | |
| 1974 | + | nil, // 25: ermite.CompleteJobRequest.OutputsEntry | |
| 1975 | + | (*durationpb.Duration)(nil), // 26: google.protobuf.Duration | |
| 1976 | + | (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp | |
| 1562 | 1977 | } | |
| 1563 | 1978 | var file_ermite_runner_proto_depIdxs = []int32{ | |
| 1564 | 1979 | 3, // 0: ermite.RegisterRunnerRequest.isolation:type_name -> ermite.RunnerIsolation | |
| 1565 | 1980 | 9, // 1: ermite.FetchJobResponse.job:type_name -> ermite.Job | |
| 1566 | - | 11, // 2: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate | |
| 1567 | - | 20, // 3: ermite.UpdateJobRequest.outputs:type_name -> ermite.UpdateJobRequest.OutputsEntry | |
| 1568 | - | 1, // 4: ermite.UpdateJobRequest.conclusion:type_name -> ermite.CiConclusion | |
| 1569 | - | 2, // 5: ermite.StepUpdate.state:type_name -> ermite.StepState | |
| 1570 | - | 1, // 6: ermite.StepUpdate.conclusion:type_name -> ermite.CiConclusion | |
| 1571 | - | 21, // 7: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp | |
| 1572 | - | 21, // 8: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp | |
| 1573 | - | 14, // 9: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine | |
| 1574 | - | 21, // 10: ermite.LogLine.time:type_name -> google.protobuf.Timestamp | |
| 1575 | - | 4, // 11: ermite.WatchJobResponse.reason:type_name -> ermite.CancelReason | |
| 1576 | - | 21, // 12: ermite.WatchJobResponse.requested_at:type_name -> google.protobuf.Timestamp | |
| 1577 | - | 5, // 13: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest | |
| 1578 | - | 7, // 14: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest | |
| 1579 | - | 10, // 15: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest | |
| 1580 | - | 13, // 16: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest | |
| 1581 | - | 16, // 17: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest | |
| 1582 | - | 18, // 18: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest | |
| 1583 | - | 6, // 19: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse | |
| 1584 | - | 8, // 20: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse | |
| 1585 | - | 12, // 21: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse | |
| 1586 | - | 15, // 22: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse | |
| 1587 | - | 17, // 23: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse | |
| 1588 | - | 19, // 24: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse | |
| 1589 | - | 19, // [19:25] is the sub-list for method output_type | |
| 1590 | - | 13, // [13:19] is the sub-list for method input_type | |
| 1591 | - | 13, // [13:13] is the sub-list for extension type_name | |
| 1592 | - | 13, // [13:13] is the sub-list for extension extendee | |
| 1593 | - | 0, // [0:13] is the sub-list for field type_name | |
| 1981 | + | 26, // 2: ermite.FetchJobResponse.retry_after:type_name -> google.protobuf.Duration | |
| 1982 | + | 11, // 3: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate | |
| 1983 | + | 2, // 4: ermite.StepUpdate.state:type_name -> ermite.StepState | |
| 1984 | + | 1, // 5: ermite.StepUpdate.conclusion:type_name -> ermite.CiConclusion | |
| 1985 | + | 27, // 6: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp | |
| 1986 | + | 27, // 7: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp | |
| 1987 | + | 1, // 8: ermite.CompleteJobRequest.conclusion:type_name -> ermite.CiConclusion | |
| 1988 | + | 25, // 9: ermite.CompleteJobRequest.outputs:type_name -> ermite.CompleteJobRequest.OutputsEntry | |
| 1989 | + | 11, // 10: ermite.CompleteJobRequest.steps:type_name -> ermite.StepUpdate | |
| 1990 | + | 1, // 11: ermite.CompleteJobResponse.conclusion:type_name -> ermite.CiConclusion | |
| 1991 | + | 1, // 12: ermite.JobEnded.conclusion:type_name -> ermite.CiConclusion | |
| 1992 | + | 17, // 13: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine | |
| 1993 | + | 27, // 14: ermite.LogLine.time:type_name -> google.protobuf.Timestamp | |
| 1994 | + | 23, // 15: ermite.WatchJobResponse.keepalive:type_name -> ermite.Keepalive | |
| 1995 | + | 24, // 16: ermite.WatchJobResponse.cancel:type_name -> ermite.CancelRequest | |
| 1996 | + | 15, // 17: ermite.WatchJobResponse.ended:type_name -> ermite.JobEnded | |
| 1997 | + | 4, // 18: ermite.CancelRequest.reason:type_name -> ermite.CancelReason | |
| 1998 | + | 27, // 19: ermite.CancelRequest.requested_at:type_name -> google.protobuf.Timestamp | |
| 1999 | + | 5, // 20: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest | |
| 2000 | + | 7, // 21: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest | |
| 2001 | + | 10, // 22: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest | |
| 2002 | + | 16, // 23: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest | |
| 2003 | + | 19, // 24: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest | |
| 2004 | + | 21, // 25: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest | |
| 2005 | + | 13, // 26: ermite.RunnerService.CompleteJob:input_type -> ermite.CompleteJobRequest | |
| 2006 | + | 6, // 27: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse | |
| 2007 | + | 8, // 28: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse | |
| 2008 | + | 12, // 29: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse | |
| 2009 | + | 18, // 30: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse | |
| 2010 | + | 20, // 31: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse | |
| 2011 | + | 22, // 32: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse | |
| 2012 | + | 14, // 33: ermite.RunnerService.CompleteJob:output_type -> ermite.CompleteJobResponse | |
| 2013 | + | 27, // [27:34] is the sub-list for method output_type | |
| 2014 | + | 20, // [20:27] is the sub-list for method input_type | |
| 2015 | + | 20, // [20:20] is the sub-list for extension type_name | |
| 2016 | + | 20, // [20:20] is the sub-list for extension extendee | |
| 2017 | + | 0, // [0:20] is the sub-list for field type_name | |
| 1594 | 2018 | } | |
| 1595 | 2019 | ||
| 1596 | 2020 | func init() { file_ermite_runner_proto_init() } | |
| @@ -1598,13 +2022,18 @@ func file_ermite_runner_proto_init() { | |||
| 1598 | 2022 | if File_ermite_runner_proto != nil { | |
| 1599 | 2023 | return | |
| 1600 | 2024 | } | |
| 2025 | + | file_ermite_runner_proto_msgTypes[17].OneofWrappers = []any{ | |
| 2026 | + | (*WatchJobResponse_Keepalive)(nil), | |
| 2027 | + | (*WatchJobResponse_Cancel)(nil), | |
| 2028 | + | (*WatchJobResponse_Ended)(nil), | |
| 2029 | + | } | |
| 1601 | 2030 | type x struct{} | |
| 1602 | 2031 | out := protoimpl.TypeBuilder{ | |
| 1603 | 2032 | File: protoimpl.DescBuilder{ | |
| 1604 | 2033 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | |
| 1605 | 2034 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)), | |
| 1606 | 2035 | NumEnums: 5, | |
| 1607 | - | NumMessages: 16, | |
| 2036 | + | NumMessages: 21, | |
| 1608 | 2037 | NumExtensions: 0, | |
| 1609 | 2038 | NumServices: 1, | |
| 1610 | 2039 | }, | |
| @@ -11,6 +11,7 @@ package ermite | |||||
| 11 | import ( | 11 | import ( | ||
| 12 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" | 12 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" | ||
| 13 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" | 13 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" | ||
| 14 | + | durationpb "google.golang.org/protobuf/types/known/durationpb" | |||
| 14 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" | 15 | timestamppb "google.golang.org/protobuf/types/known/timestamppb" | ||
| 15 | reflect "reflect" | 16 | reflect "reflect" | ||
| 16 | sync "sync" | 17 | sync "sync" | ||
| @@ -287,8 +288,7 @@ func (RunnerIsolation) EnumDescriptor() ([]byte, []int) { | |||||
| 287 | type CancelReason int32 | 288 | type CancelReason int32 | ||
| 288 | 289 | ||||
| 289 | const ( | 290 | const ( | ||
| 290 | - | // Not set. A WatchJobResponse with this reason is a keep-alive and carries | 291 | + | // Not set. Never sent in a CancelRequest. |
| 291 | - | // no request. | |||
| 292 | CancelReason_CANCEL_REASON_UNSPECIFIED CancelReason = 0 | 292 | CancelReason_CANCEL_REASON_UNSPECIFIED CancelReason = 0 | ||
| 293 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | 293 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | ||
| 294 | // ends CI_CONCLUSION_CANCELLED. | 294 | // ends CI_CONCLUSION_CANCELLED. | ||
| @@ -296,8 +296,8 @@ const ( | |||||
| 296 | // Another job of the same matrix failed and the strategy has fail-fast. The | 296 | // Another job of the same matrix failed and the strategy has fail-fast. The | ||
| 297 | // job ends CI_CONCLUSION_CANCELLED. | 297 | // job ends CI_CONCLUSION_CANCELLED. | ||
| 298 | CancelReason_CANCEL_REASON_FAIL_FAST CancelReason = 2 | 298 | CancelReason_CANCEL_REASON_FAIL_FAST CancelReason = 2 | ||
| 299 | - | // The job exceeded its timeout_minutes without the runner ending it. The | 299 | + | // The job exceeded its time budget (see Job.timeout_minutes). The job ends |
| 300 | - | // job ends CI_CONCLUSION_TIMED_OUT. | 300 | + | // CI_CONCLUSION_TIMED_OUT. |
| 301 | CancelReason_CANCEL_REASON_TIMEOUT CancelReason = 3 | 301 | CancelReason_CANCEL_REASON_TIMEOUT CancelReason = 3 | ||
| 302 | ) | 302 | ) | ||
| 303 | 303 | ||||
| @@ -367,7 +367,14 @@ type RegisterRunnerRequest struct { | |||||
| 367 | Arch string `protobuf:"bytes,5,opt,name=arch,proto3" json:"arch,omitempty"` | 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 | 368 | // Os is the operating system: "linux". Other values are invalid_argument | ||
| 369 | // (the set may grow with compatible additions). | 369 | // (the set may grow with compatible additions). | ||
| 370 | - | Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"` | 370 | + | Os string `protobuf:"bytes,6,opt,name=os,proto3" json:"os,omitempty"` |
| 371 | + | // Replace removes the runner that already has this name, if any, before | |||
| 372 | + | // registering this one: its runner token is revoked at once, as in | |||
| 373 | + | // RunService.RemoveRunner, and a job it is running is not affected. The new | |||
| 374 | + | // runner gets a new runner_id. Needs the same scope as registering. Without | |||
| 375 | + | // replace, a name in use is already_exists. It is how a runner re-registers | |||
| 376 | + | // after losing its state. | |||
| 377 | + | Replace bool `protobuf:"varint,7,opt,name=replace,proto3" json:"replace,omitempty"` | |||
| 371 | unknownFields protoimpl.UnknownFields | 378 | unknownFields protoimpl.UnknownFields | ||
| 372 | sizeCache protoimpl.SizeCache | 379 | sizeCache protoimpl.SizeCache | ||
| 373 | } | 380 | } | ||
| @@ -444,6 +451,13 @@ func (x *RegisterRunnerRequest) GetOs() string { | |||||
| 444 | return "" | 451 | return "" | ||
| 445 | } | 452 | } | ||
| 446 | 453 | ||||
| 454 | + | func (x *RegisterRunnerRequest) GetReplace() bool { | |||
| 455 | + | if x != nil { | |||
| 456 | + | return x.Replace | |||
| 457 | + | } | |||
| 458 | + | return false | |||
| 459 | + | } | |||
| 460 | + | ||||
| 447 | // RegisterRunnerResponse holds the new runner and its secret. | 461 | // RegisterRunnerResponse holds the new runner and its secret. | ||
| 448 | type RegisterRunnerResponse struct { | 462 | type RegisterRunnerResponse struct { | ||
| 449 | state protoimpl.MessageState `protogen:"open.v1"` | 463 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| @@ -451,9 +465,10 @@ type RegisterRunnerResponse struct { | |||||
| 451 | // a secret. | 465 | // a secret. | ||
| 452 | RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` | 466 | 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: | 467 | // RunnerToken is the secret of the runner, to present as "Authorization: | ||
| 454 | - | // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is | 468 | + | // Bearer <runner_token>" to FetchJob: "ermiterun_" and 43 base64url |
| 455 | - | // returned once: the server keeps only its SHA-256, the runner must store | 469 | + | // characters (32 random bytes, no padding). It is returned once: the server |
| 456 | - | // it (mode 0600). It stops working when the runner is removed. | 470 | + | // keeps only its SHA-256, the runner must store it (mode 0600). It stops |
| 471 | + | // working when the runner is removed or replaced. | |||
| 457 | RunnerToken string `protobuf:"bytes,2,opt,name=runner_token,json=runnerToken,proto3" json:"runner_token,omitempty"` | 472 | RunnerToken string `protobuf:"bytes,2,opt,name=runner_token,json=runnerToken,proto3" json:"runner_token,omitempty"` | ||
| 458 | unknownFields protoimpl.UnknownFields | 473 | unknownFields protoimpl.UnknownFields | ||
| 459 | sizeCache protoimpl.SizeCache | 474 | sizeCache protoimpl.SizeCache | ||
| @@ -510,7 +525,14 @@ type FetchJobRequest struct { | |||||
| 510 | // labels of the runner, which is the normal case. Otherwise each one must be | 525 | // 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 | 526 | // a label of the runner (invalid_argument if not) and only jobs whose runs-on | ||
| 512 | // labels are all among them are offered. | 527 | // labels are all among them are offered. | ||
| 513 | - | Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` | 528 | + | Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` |
| 529 | + | // PollId identifies this poll, chosen by the client (for example 16 random | |||
| 530 | + | // bytes in base64url), 1 to 64 characters among letters, digits, "_" and | |||
| 531 | + | // "-". A client that retries a call whose response it did not get sends the | |||
| 532 | + | // same poll_id, and gets the assignment the first call made, if it made one | |||
| 533 | + | // (see FetchJob). A new poll uses a new id. Empty means no retry protection: | |||
| 534 | + | // a lost assignment is then recovered by the acknowledgement timeout alone. | |||
| 535 | + | PollId string `protobuf:"bytes,2,opt,name=poll_id,json=pollId,proto3" json:"poll_id,omitempty"` | |||
| 514 | unknownFields protoimpl.UnknownFields | 536 | unknownFields protoimpl.UnknownFields | ||
| 515 | sizeCache protoimpl.SizeCache | 537 | sizeCache protoimpl.SizeCache | ||
| 516 | } | 538 | } | ||
| @@ -552,12 +574,24 @@ func (x *FetchJobRequest) GetLabels() []string { | |||||
| 552 | return nil | 574 | return nil | ||
| 553 | } | 575 | } | ||
| 554 | 576 | ||||
| 577 | + | func (x *FetchJobRequest) GetPollId() string { | |||
| 578 | + | if x != nil { | |||
| 579 | + | return x.PollId | |||
| 580 | + | } | |||
| 581 | + | return "" | |||
| 582 | + | } | |||
| 583 | + | ||||
| 555 | // FetchJobResponse holds the job given to the runner, if any. | 584 | // FetchJobResponse holds the job given to the runner, if any. | ||
| 556 | type FetchJobResponse struct { | 585 | type FetchJobResponse struct { | ||
| 557 | state protoimpl.MessageState `protogen:"open.v1"` | 586 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| 558 | // Job is the job to run. Unset when no job was available before the end of | 587 | // Job is the job to run. Unset when no job was available before the end of | ||
| 559 | // the poll. | 588 | // the poll. | ||
| 560 | - | Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` | 589 | + | Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` |
| 590 | + | // RetryAfter is how long the client waits before its next FetchJob. Unset or | |||
| 591 | + | // zero means call again at once. The server sets it when it has no job for | |||
| 592 | + | // the runner and wants to slow polling down (load, shutdown in progress); | |||
| 593 | + | // clients obey it and may wait longer, never less. Unset when job is set. | |||
| 594 | + | RetryAfter *durationpb.Duration `protobuf:"bytes,2,opt,name=retry_after,json=retryAfter,proto3" json:"retry_after,omitempty"` | |||
| 561 | unknownFields protoimpl.UnknownFields | 595 | unknownFields protoimpl.UnknownFields | ||
| 562 | sizeCache protoimpl.SizeCache | 596 | sizeCache protoimpl.SizeCache | ||
| 563 | } | 597 | } | ||
| @@ -599,6 +633,13 @@ func (x *FetchJobResponse) GetJob() *Job { | |||||
| 599 | return nil | 633 | return nil | ||
| 600 | } | 634 | } | ||
| 601 | 635 | ||||
| 636 | + | func (x *FetchJobResponse) GetRetryAfter() *durationpb.Duration { | |||
| 637 | + | if x != nil { | |||
| 638 | + | return x.RetryAfter | |||
| 639 | + | } | |||
| 640 | + | return nil | |||
| 641 | + | } | |||
| 642 | + | ||||
| 602 | // Job is a job given to a runner: everything needed to run it, nothing more. | 643 | // 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 | 644 | // The runner parses workflow_yaml itself; the protocol does not carry the | ||
| 604 | // workflow model, so it does not change when the model grows. | 645 | // workflow model, so it does not change when the model grows. | ||
| @@ -615,10 +656,13 @@ type Job struct { | |||||
| 615 | // one more after each RunService.RerunRun. It is the "github.run_attempt". | 656 | // 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"` | 657 | Attempt int32 `protobuf:"varint,3,opt,name=attempt,proto3" json:"attempt,omitempty"` | ||
| 617 | // JobToken is the secret that authenticates this job's UpdateJob, | 658 | // JobToken is the secret that authenticates this job's UpdateJob, | ||
| 618 | - | // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer | 659 | + | // AppendLog, Heartbeat, WatchJob and CompleteJob calls ("Authorization: |
| 619 | - | // <job_token>"). It is random, valid until the job ends, survives a server | 660 | + | // Bearer <job_token>"): "ermitejob_" and 43 base64url characters (32 random |
| 620 | - | // restart and is shown only in this response. It must stay in the runner | 661 | + | // bytes, no padding). It is random, valid from the assignment until the job |
| 621 | - | // host process and never reach the job's code. | 662 | + | // ends (and for CompleteJob 24 hours more), survives a server restart and |
| 663 | + | // is shown only in this response, or in a repeat of the same poll_id. It | |||
| 664 | + | // must stay in the runner host process and never reach the job's code. A new | |||
| 665 | + | // assignment of the same job has a new token. | |||
| 622 | JobToken string `protobuf:"bytes,4,opt,name=job_token,json=jobToken,proto3" json:"job_token,omitempty"` | 666 | 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 | 667 | // WorkflowPath is the path of the workflow file in the repository, for | ||
| 624 | // example ".github/workflows/ci.yml". | 668 | // example ".github/workflows/ci.yml". | ||
| @@ -643,8 +687,11 @@ type Job struct { | |||||
| 643 | Labels []string `protobuf:"bytes,10,rep,name=labels,proto3" json:"labels,omitempty"` | 687 | 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 | 688 | // 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 | 689 | // 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 | 690 | + | // runner enforces it from the moment it receives the job. The server counts |
| 647 | - | // exceeds it by announcing CANCEL_REASON_TIMEOUT. | 691 | + | // from the moment of the assignment and adds 60 seconds of slack, so the |
| 692 | + | // runner's own timeout always comes first: when the server's deadline | |||
| 693 | + | // passes it announces CANCEL_REASON_TIMEOUT, and ends the job itself after | |||
| 694 | + | // 60 more seconds without a conclusion (see WatchJob). | |||
| 648 | TimeoutMinutes int32 `protobuf:"varint,11,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"` | 695 | TimeoutMinutes int32 `protobuf:"varint,11,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"` | ||
| 649 | // Repo is the name of the repository. | 696 | // Repo is the name of the repository. | ||
| 650 | Repo string `protobuf:"bytes,12,opt,name=repo,proto3" json:"repo,omitempty"` | 697 | Repo string `protobuf:"bytes,12,opt,name=repo,proto3" json:"repo,omitempty"` | ||
| @@ -786,7 +833,7 @@ func (x *Job) GetRef() string { | |||||
| 786 | return "" | 833 | return "" | ||
| 787 | } | 834 | } | ||
| 788 | 835 | ||||
| 789 | - | // UpdateJobRequest reports the steps and, in the last call, the end of a job. | 836 | + | // UpdateJobRequest reports the steps of a running job. |
| 790 | type UpdateJobRequest struct { | 837 | type UpdateJobRequest struct { | ||
| 791 | state protoimpl.MessageState `protogen:"open.v1"` | 838 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| 792 | // JobId is the Job.job_id. It must be the job of the token. | 839 | // JobId is the Job.job_id. It must be the job of the token. | ||
| @@ -796,31 +843,9 @@ type UpdateJobRequest struct { | |||||
| 796 | // (invalid_argument beyond). Steps are numbered like GitHub's log: the | 843 | // (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 | 844 | // implicit "Set up job" is 1, the steps of the workflow follow in order, and | ||
| 798 | // the implicit "Complete job" is last. | 845 | // the implicit "Complete job" is last. | ||
| 799 | - | Steps []*StepUpdate `protobuf:"bytes,2,rep,name=steps,proto3" json:"steps,omitempty"` | 846 | + | 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 | 847 | + | unknownFields protoimpl.UnknownFields |
| 801 | - | // in the call that carries the conclusion (invalid_argument otherwise). | 848 | + | sizeCache protoimpl.SizeCache |
| 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 CI_CONCLUSION_SUCCESS, | |||
| 810 | - | // CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or CI_CONCLUSION_TIMED_OUT; any | |||
| 811 | - | // other value is invalid_argument. CI_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 CI_CONCLUSION_CANCELLED (or SKIPPED when they | |||
| 814 | - | // never started and the job did not fail by cancellation). | |||
| 815 | - | Conclusion CiConclusion `protobuf:"varint,4,opt,name=conclusion,proto3,enum=ermite.CiConclusion" 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 CI_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 | } | 849 | } | ||
| 825 | 850 | ||||
| 826 | func (x *UpdateJobRequest) Reset() { | 851 | func (x *UpdateJobRequest) Reset() { | ||
| @@ -867,27 +892,6 @@ func (x *UpdateJobRequest) GetSteps() []*StepUpdate { | |||||
| 867 | return nil | 892 | return nil | ||
| 868 | } | 893 | } | ||
| 869 | 894 | ||||
| 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() CiConclusion { | |||
| 878 | - | if x != nil { | |||
| 879 | - | return x.Conclusion | |||
| 880 | - | } | |||
| 881 | - | return CiConclusion_CI_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. | 895 | // StepUpdate is the state of one step as the runner knows it. | ||
| 892 | type StepUpdate struct { | 896 | type StepUpdate struct { | ||
| 893 | state protoimpl.MessageState `protogen:"open.v1"` | 897 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| @@ -1023,14 +1027,237 @@ func (*UpdateJobResponse) Descriptor() ([]byte, []int) { | |||||
| 1023 | return file_ermite_runner_proto_rawDescGZIP(), []int{7} | 1027 | return file_ermite_runner_proto_rawDescGZIP(), []int{7} | ||
| 1024 | } | 1028 | } | ||
| 1025 | 1029 | ||||
| 1030 | + | // CompleteJobRequest ends a job. | |||
| 1031 | + | type CompleteJobRequest struct { | |||
| 1032 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1033 | + | // JobId is the Job.job_id. It must be the job of the token. | |||
| 1034 | + | JobId string `protobuf:"bytes,1,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` | |||
| 1035 | + | // Conclusion is how the job ended. Required: a runner reports | |||
| 1036 | + | // CI_CONCLUSION_SUCCESS, CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or | |||
| 1037 | + | // CI_CONCLUSION_TIMED_OUT; any other value is invalid_argument. Skipped is | |||
| 1038 | + | // decided by the server and never reported by a runner. | |||
| 1039 | + | Conclusion CiConclusion `protobuf:"varint,2,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |||
| 1040 | + | // Outputs are the outputs of the job, evaluated by the runner. Names are 1 | |||
| 1041 | + | // to 255 characters among letters, digits, "_" and "-" and begin with a | |||
| 1042 | + | // letter or "_" and are case-insensitive: two names that differ only by case | |||
| 1043 | + | // are invalid_argument. Each value is at most 1 MiB, and all names and | |||
| 1044 | + | // values together at most 1 MiB (1,048,576 bytes); beyond that the whole call | |||
| 1045 | + | // is rejected with invalid_argument and the runner reports the failure of | |||
| 1046 | + | // the job without outputs, with a line in the log. | |||
| 1047 | + | 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"` | |||
| 1048 | + | // RunnerShutdown is true when the runner ends the job because it was told to | |||
| 1049 | + | // stop (service stopped, signal). Only allowed with CI_CONCLUSION_CANCELLED. | |||
| 1050 | + | // The server then annotates the job with GitHub's message "The runner has | |||
| 1051 | + | // received a shutdown signal. This can happen when the runner service is | |||
| 1052 | + | // stopped, or a manually started runner is canceled." The conclusion | |||
| 1053 | + | // CANCELLED for this case is to be verified against a recording of GitHub. | |||
| 1054 | + | RunnerShutdown bool `protobuf:"varint,4,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"` | |||
| 1055 | + | // Steps are the final steps, with the same meaning as UpdateJobRequest.steps, | |||
| 1056 | + | // applied in the same atomic step as the conclusion. | |||
| 1057 | + | Steps []*StepUpdate `protobuf:"bytes,5,rep,name=steps,proto3" json:"steps,omitempty"` | |||
| 1058 | + | unknownFields protoimpl.UnknownFields | |||
| 1059 | + | sizeCache protoimpl.SizeCache | |||
| 1060 | + | } | |||
| 1061 | + | ||||
| 1062 | + | func (x *CompleteJobRequest) Reset() { | |||
| 1063 | + | *x = CompleteJobRequest{} | |||
| 1064 | + | mi := &file_ermite_runner_proto_msgTypes[8] | |||
| 1065 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1066 | + | ms.StoreMessageInfo(mi) | |||
| 1067 | + | } | |||
| 1068 | + | ||||
| 1069 | + | func (x *CompleteJobRequest) String() string { | |||
| 1070 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1071 | + | } | |||
| 1072 | + | ||||
| 1073 | + | func (*CompleteJobRequest) ProtoMessage() {} | |||
| 1074 | + | ||||
| 1075 | + | func (x *CompleteJobRequest) ProtoReflect() protoreflect.Message { | |||
| 1076 | + | mi := &file_ermite_runner_proto_msgTypes[8] | |||
| 1077 | + | if x != nil { | |||
| 1078 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1079 | + | if ms.LoadMessageInfo() == nil { | |||
| 1080 | + | ms.StoreMessageInfo(mi) | |||
| 1081 | + | } | |||
| 1082 | + | return ms | |||
| 1083 | + | } | |||
| 1084 | + | return mi.MessageOf(x) | |||
| 1085 | + | } | |||
| 1086 | + | ||||
| 1087 | + | // Deprecated: Use CompleteJobRequest.ProtoReflect.Descriptor instead. | |||
| 1088 | + | func (*CompleteJobRequest) Descriptor() ([]byte, []int) { | |||
| 1089 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{8} | |||
| 1090 | + | } | |||
| 1091 | + | ||||
| 1092 | + | func (x *CompleteJobRequest) GetJobId() string { | |||
| 1093 | + | if x != nil { | |||
| 1094 | + | return x.JobId | |||
| 1095 | + | } | |||
| 1096 | + | return "" | |||
| 1097 | + | } | |||
| 1098 | + | ||||
| 1099 | + | func (x *CompleteJobRequest) GetConclusion() CiConclusion { | |||
| 1100 | + | if x != nil { | |||
| 1101 | + | return x.Conclusion | |||
| 1102 | + | } | |||
| 1103 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |||
| 1104 | + | } | |||
| 1105 | + | ||||
| 1106 | + | func (x *CompleteJobRequest) GetOutputs() map[string]string { | |||
| 1107 | + | if x != nil { | |||
| 1108 | + | return x.Outputs | |||
| 1109 | + | } | |||
| 1110 | + | return nil | |||
| 1111 | + | } | |||
| 1112 | + | ||||
| 1113 | + | func (x *CompleteJobRequest) GetRunnerShutdown() bool { | |||
| 1114 | + | if x != nil { | |||
| 1115 | + | return x.RunnerShutdown | |||
| 1116 | + | } | |||
| 1117 | + | return false | |||
| 1118 | + | } | |||
| 1119 | + | ||||
| 1120 | + | func (x *CompleteJobRequest) GetSteps() []*StepUpdate { | |||
| 1121 | + | if x != nil { | |||
| 1122 | + | return x.Steps | |||
| 1123 | + | } | |||
| 1124 | + | return nil | |||
| 1125 | + | } | |||
| 1126 | + | ||||
| 1127 | + | // CompleteJobResponse is the stored result of a job. | |||
| 1128 | + | type CompleteJobResponse struct { | |||
| 1129 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1130 | + | // Conclusion is the conclusion of the job as the server stored it. It is the | |||
| 1131 | + | // one of the request when applied is true. | |||
| 1132 | + | Conclusion CiConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |||
| 1133 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |||
| 1134 | + | // there is none. | |||
| 1135 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |||
| 1136 | + | // Applied is true when this call ended the job, false when the job had | |||
| 1137 | + | // already ended (a retry, or the server ended it first) and nothing changed. | |||
| 1138 | + | Applied bool `protobuf:"varint,3,opt,name=applied,proto3" json:"applied,omitempty"` | |||
| 1139 | + | unknownFields protoimpl.UnknownFields | |||
| 1140 | + | sizeCache protoimpl.SizeCache | |||
| 1141 | + | } | |||
| 1142 | + | ||||
| 1143 | + | func (x *CompleteJobResponse) Reset() { | |||
| 1144 | + | *x = CompleteJobResponse{} | |||
| 1145 | + | mi := &file_ermite_runner_proto_msgTypes[9] | |||
| 1146 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1147 | + | ms.StoreMessageInfo(mi) | |||
| 1148 | + | } | |||
| 1149 | + | ||||
| 1150 | + | func (x *CompleteJobResponse) String() string { | |||
| 1151 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1152 | + | } | |||
| 1153 | + | ||||
| 1154 | + | func (*CompleteJobResponse) ProtoMessage() {} | |||
| 1155 | + | ||||
| 1156 | + | func (x *CompleteJobResponse) ProtoReflect() protoreflect.Message { | |||
| 1157 | + | mi := &file_ermite_runner_proto_msgTypes[9] | |||
| 1158 | + | if x != nil { | |||
| 1159 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1160 | + | if ms.LoadMessageInfo() == nil { | |||
| 1161 | + | ms.StoreMessageInfo(mi) | |||
| 1162 | + | } | |||
| 1163 | + | return ms | |||
| 1164 | + | } | |||
| 1165 | + | return mi.MessageOf(x) | |||
| 1166 | + | } | |||
| 1167 | + | ||||
| 1168 | + | // Deprecated: Use CompleteJobResponse.ProtoReflect.Descriptor instead. | |||
| 1169 | + | func (*CompleteJobResponse) Descriptor() ([]byte, []int) { | |||
| 1170 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{9} | |||
| 1171 | + | } | |||
| 1172 | + | ||||
| 1173 | + | func (x *CompleteJobResponse) GetConclusion() CiConclusion { | |||
| 1174 | + | if x != nil { | |||
| 1175 | + | return x.Conclusion | |||
| 1176 | + | } | |||
| 1177 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |||
| 1178 | + | } | |||
| 1179 | + | ||||
| 1180 | + | func (x *CompleteJobResponse) GetError() string { | |||
| 1181 | + | if x != nil { | |||
| 1182 | + | return x.Error | |||
| 1183 | + | } | |||
| 1184 | + | return "" | |||
| 1185 | + | } | |||
| 1186 | + | ||||
| 1187 | + | func (x *CompleteJobResponse) GetApplied() bool { | |||
| 1188 | + | if x != nil { | |||
| 1189 | + | return x.Applied | |||
| 1190 | + | } | |||
| 1191 | + | return false | |||
| 1192 | + | } | |||
| 1193 | + | ||||
| 1194 | + | // JobEnded says that a job has ended. It is sent in WatchJob and attached, as | |||
| 1195 | + | // an error detail, to the failed_precondition errors that RunnerService returns | |||
| 1196 | + | // for a job that has ended. | |||
| 1197 | + | type JobEnded struct { | |||
| 1198 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1199 | + | // Conclusion is how the job ended. | |||
| 1200 | + | Conclusion CiConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.CiConclusion" json:"conclusion,omitempty"` | |||
| 1201 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |||
| 1202 | + | // there is none. | |||
| 1203 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |||
| 1204 | + | unknownFields protoimpl.UnknownFields | |||
| 1205 | + | sizeCache protoimpl.SizeCache | |||
| 1206 | + | } | |||
| 1207 | + | ||||
| 1208 | + | func (x *JobEnded) Reset() { | |||
| 1209 | + | *x = JobEnded{} | |||
| 1210 | + | mi := &file_ermite_runner_proto_msgTypes[10] | |||
| 1211 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1212 | + | ms.StoreMessageInfo(mi) | |||
| 1213 | + | } | |||
| 1214 | + | ||||
| 1215 | + | func (x *JobEnded) String() string { | |||
| 1216 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1217 | + | } | |||
| 1218 | + | ||||
| 1219 | + | func (*JobEnded) ProtoMessage() {} | |||
| 1220 | + | ||||
| 1221 | + | func (x *JobEnded) ProtoReflect() protoreflect.Message { | |||
| 1222 | + | mi := &file_ermite_runner_proto_msgTypes[10] | |||
| 1223 | + | if x != nil { | |||
| 1224 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1225 | + | if ms.LoadMessageInfo() == nil { | |||
| 1226 | + | ms.StoreMessageInfo(mi) | |||
| 1227 | + | } | |||
| 1228 | + | return ms | |||
| 1229 | + | } | |||
| 1230 | + | return mi.MessageOf(x) | |||
| 1231 | + | } | |||
| 1232 | + | ||||
| 1233 | + | // Deprecated: Use JobEnded.ProtoReflect.Descriptor instead. | |||
| 1234 | + | func (*JobEnded) Descriptor() ([]byte, []int) { | |||
| 1235 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{10} | |||
| 1236 | + | } | |||
| 1237 | + | ||||
| 1238 | + | func (x *JobEnded) GetConclusion() CiConclusion { | |||
| 1239 | + | if x != nil { | |||
| 1240 | + | return x.Conclusion | |||
| 1241 | + | } | |||
| 1242 | + | return CiConclusion_CI_CONCLUSION_UNSPECIFIED | |||
| 1243 | + | } | |||
| 1244 | + | ||||
| 1245 | + | func (x *JobEnded) GetError() string { | |||
| 1246 | + | if x != nil { | |||
| 1247 | + | return x.Error | |||
| 1248 | + | } | |||
| 1249 | + | return "" | |||
| 1250 | + | } | |||
| 1251 | + | ||||
| 1026 | // AppendLogRequest carries log lines of one step. | 1252 | // AppendLogRequest carries log lines of one step. | ||
| 1027 | type AppendLogRequest struct { | 1253 | type AppendLogRequest struct { | ||
| 1028 | state protoimpl.MessageState `protogen:"open.v1"` | 1254 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| 1029 | // JobId is the Job.job_id. It must be the job of the token. | 1255 | // 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"` | 1256 | 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 | 1257 | // Step is the number of the step the lines belong to (see | ||
| 1032 | - | // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not | 1258 | + | // UpdateJobRequest.steps), at least 1, and at least the step of the last |
| 1033 | - | // announced yet is created by the first lines. | 1259 | + | // stored line. A step that UpdateJob has not announced yet is created by the |
| 1260 | + | // first lines. See the rules on AppendLog. | |||
| 1034 | Step int32 `protobuf:"varint,2,opt,name=step,proto3" json:"step,omitempty"` | 1261 | 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 | 1262 | // 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. | 1263 | // job, counted from 0 across all steps. See the rules on AppendLog. | ||
| @@ -1044,7 +1271,7 @@ type AppendLogRequest struct { | |||||
| 1044 | 1271 | ||||
| 1045 | func (x *AppendLogRequest) Reset() { | 1272 | func (x *AppendLogRequest) Reset() { | ||
| 1046 | *x = AppendLogRequest{} | 1273 | *x = AppendLogRequest{} | ||
| 1047 | - | mi := &file_ermite_runner_proto_msgTypes[8] | 1274 | + | mi := &file_ermite_runner_proto_msgTypes[11] |
| 1048 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1275 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1049 | ms.StoreMessageInfo(mi) | 1276 | ms.StoreMessageInfo(mi) | ||
| 1050 | } | 1277 | } | ||
| @@ -1056,7 +1283,7 @@ func (x *AppendLogRequest) String() string { | |||||
| 1056 | func (*AppendLogRequest) ProtoMessage() {} | 1283 | func (*AppendLogRequest) ProtoMessage() {} | ||
| 1057 | 1284 | ||||
| 1058 | func (x *AppendLogRequest) ProtoReflect() protoreflect.Message { | 1285 | func (x *AppendLogRequest) ProtoReflect() protoreflect.Message { | ||
| 1059 | - | mi := &file_ermite_runner_proto_msgTypes[8] | 1286 | + | mi := &file_ermite_runner_proto_msgTypes[11] |
| 1060 | if x != nil { | 1287 | if x != nil { | ||
| 1061 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1288 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1062 | if ms.LoadMessageInfo() == nil { | 1289 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1069,7 +1296,7 @@ func (x *AppendLogRequest) ProtoReflect() protoreflect.Message { | |||||
| 1069 | 1296 | ||||
| 1070 | // Deprecated: Use AppendLogRequest.ProtoReflect.Descriptor instead. | 1297 | // Deprecated: Use AppendLogRequest.ProtoReflect.Descriptor instead. | ||
| 1071 | func (*AppendLogRequest) Descriptor() ([]byte, []int) { | 1298 | func (*AppendLogRequest) Descriptor() ([]byte, []int) { | ||
| 1072 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{8} | 1299 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{11} |
| 1073 | } | 1300 | } | ||
| 1074 | 1301 | ||||
| 1075 | func (x *AppendLogRequest) GetJobId() string { | 1302 | func (x *AppendLogRequest) GetJobId() string { | ||
| @@ -1110,14 +1337,23 @@ type LogLine struct { | |||||
| 1110 | // replaces invalid bytes with U+FFFD). It must not contain a line feed; a | 1337 | // 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, | 1338 | // carriage return is kept. It is the line as GitHub's raw log has it, | ||
| 1112 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | 1339 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | ||
| 1113 | - | Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` | 1340 | + | Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` |
| 1341 | + | // Continued is true when this line is the continuation of the line before | |||
| 1342 | + | // it, which was too long to send whole. A runner cuts a line of more than | |||
| 1343 | + | // 64 KiB into pieces at UTF-8 character boundaries, each piece of at most | |||
| 1344 | + | // 64 KiB, and sets continued on every piece but the first. Each piece is one | |||
| 1345 | + | // line for numbering and offsets; the server and the raw log join the | |||
| 1346 | + | // pieces back, so the original line is rebuilt exactly. The first line of a | |||
| 1347 | + | // job cannot be continued (invalid_argument) and a continued line is in | |||
| 1348 | + | // the same step as the line before it (failed_precondition otherwise). | |||
| 1349 | + | Continued bool `protobuf:"varint,3,opt,name=continued,proto3" json:"continued,omitempty"` | |||
| 1114 | unknownFields protoimpl.UnknownFields | 1350 | unknownFields protoimpl.UnknownFields | ||
| 1115 | sizeCache protoimpl.SizeCache | 1351 | sizeCache protoimpl.SizeCache | ||
| 1116 | } | 1352 | } | ||
| 1117 | 1353 | ||||
| 1118 | func (x *LogLine) Reset() { | 1354 | func (x *LogLine) Reset() { | ||
| 1119 | *x = LogLine{} | 1355 | *x = LogLine{} | ||
| 1120 | - | mi := &file_ermite_runner_proto_msgTypes[9] | 1356 | + | mi := &file_ermite_runner_proto_msgTypes[12] |
| 1121 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1357 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1122 | ms.StoreMessageInfo(mi) | 1358 | ms.StoreMessageInfo(mi) | ||
| 1123 | } | 1359 | } | ||
| @@ -1129,7 +1365,7 @@ func (x *LogLine) String() string { | |||||
| 1129 | func (*LogLine) ProtoMessage() {} | 1365 | func (*LogLine) ProtoMessage() {} | ||
| 1130 | 1366 | ||||
| 1131 | func (x *LogLine) ProtoReflect() protoreflect.Message { | 1367 | func (x *LogLine) ProtoReflect() protoreflect.Message { | ||
| 1132 | - | mi := &file_ermite_runner_proto_msgTypes[9] | 1368 | + | mi := &file_ermite_runner_proto_msgTypes[12] |
| 1133 | if x != nil { | 1369 | if x != nil { | ||
| 1134 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1370 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1135 | if ms.LoadMessageInfo() == nil { | 1371 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1142,7 +1378,7 @@ func (x *LogLine) ProtoReflect() protoreflect.Message { | |||||
| 1142 | 1378 | ||||
| 1143 | // Deprecated: Use LogLine.ProtoReflect.Descriptor instead. | 1379 | // Deprecated: Use LogLine.ProtoReflect.Descriptor instead. | ||
| 1144 | func (*LogLine) Descriptor() ([]byte, []int) { | 1380 | func (*LogLine) Descriptor() ([]byte, []int) { | ||
| 1145 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{9} | 1381 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{12} |
| 1146 | } | 1382 | } | ||
| 1147 | 1383 | ||||
| 1148 | func (x *LogLine) GetTime() *timestamppb.Timestamp { | 1384 | func (x *LogLine) GetTime() *timestamppb.Timestamp { | ||
| @@ -1159,6 +1395,13 @@ func (x *LogLine) GetText() string { | |||||
| 1159 | return "" | 1395 | return "" | ||
| 1160 | } | 1396 | } | ||
| 1161 | 1397 | ||||
| 1398 | + | func (x *LogLine) GetContinued() bool { | |||
| 1399 | + | if x != nil { | |||
| 1400 | + | return x.Continued | |||
| 1401 | + | } | |||
| 1402 | + | return false | |||
| 1403 | + | } | |||
| 1404 | + | ||||
| 1162 | // AppendLogResponse tells how much of the log the server holds. | 1405 | // AppendLogResponse tells how much of the log the server holds. | ||
| 1163 | type AppendLogResponse struct { | 1406 | type AppendLogResponse struct { | ||
| 1164 | state protoimpl.MessageState `protogen:"open.v1"` | 1407 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| @@ -1171,7 +1414,7 @@ type AppendLogResponse struct { | |||||
| 1171 | 1414 | ||||
| 1172 | func (x *AppendLogResponse) Reset() { | 1415 | func (x *AppendLogResponse) Reset() { | ||
| 1173 | *x = AppendLogResponse{} | 1416 | *x = AppendLogResponse{} | ||
| 1174 | - | mi := &file_ermite_runner_proto_msgTypes[10] | 1417 | + | mi := &file_ermite_runner_proto_msgTypes[13] |
| 1175 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1418 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1176 | ms.StoreMessageInfo(mi) | 1419 | ms.StoreMessageInfo(mi) | ||
| 1177 | } | 1420 | } | ||
| @@ -1183,7 +1426,7 @@ func (x *AppendLogResponse) String() string { | |||||
| 1183 | func (*AppendLogResponse) ProtoMessage() {} | 1426 | func (*AppendLogResponse) ProtoMessage() {} | ||
| 1184 | 1427 | ||||
| 1185 | func (x *AppendLogResponse) ProtoReflect() protoreflect.Message { | 1428 | func (x *AppendLogResponse) ProtoReflect() protoreflect.Message { | ||
| 1186 | - | mi := &file_ermite_runner_proto_msgTypes[10] | 1429 | + | mi := &file_ermite_runner_proto_msgTypes[13] |
| 1187 | if x != nil { | 1430 | if x != nil { | ||
| 1188 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1431 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1189 | if ms.LoadMessageInfo() == nil { | 1432 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1196,7 +1439,7 @@ func (x *AppendLogResponse) ProtoReflect() protoreflect.Message { | |||||
| 1196 | 1439 | ||||
| 1197 | // Deprecated: Use AppendLogResponse.ProtoReflect.Descriptor instead. | 1440 | // Deprecated: Use AppendLogResponse.ProtoReflect.Descriptor instead. | ||
| 1198 | func (*AppendLogResponse) Descriptor() ([]byte, []int) { | 1441 | func (*AppendLogResponse) Descriptor() ([]byte, []int) { | ||
| 1199 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{10} | 1442 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{13} |
| 1200 | } | 1443 | } | ||
| 1201 | 1444 | ||||
| 1202 | func (x *AppendLogResponse) GetNextOffset() int64 { | 1445 | func (x *AppendLogResponse) GetNextOffset() int64 { | ||
| @@ -1217,7 +1460,7 @@ type HeartbeatRequest struct { | |||||
| 1217 | 1460 | ||||
| 1218 | func (x *HeartbeatRequest) Reset() { | 1461 | func (x *HeartbeatRequest) Reset() { | ||
| 1219 | *x = HeartbeatRequest{} | 1462 | *x = HeartbeatRequest{} | ||
| 1220 | - | mi := &file_ermite_runner_proto_msgTypes[11] | 1463 | + | mi := &file_ermite_runner_proto_msgTypes[14] |
| 1221 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1464 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1222 | ms.StoreMessageInfo(mi) | 1465 | ms.StoreMessageInfo(mi) | ||
| 1223 | } | 1466 | } | ||
| @@ -1229,7 +1472,7 @@ func (x *HeartbeatRequest) String() string { | |||||
| 1229 | func (*HeartbeatRequest) ProtoMessage() {} | 1472 | func (*HeartbeatRequest) ProtoMessage() {} | ||
| 1230 | 1473 | ||||
| 1231 | func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { | 1474 | func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { | ||
| 1232 | - | mi := &file_ermite_runner_proto_msgTypes[11] | 1475 | + | mi := &file_ermite_runner_proto_msgTypes[14] |
| 1233 | if x != nil { | 1476 | if x != nil { | ||
| 1234 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1477 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1235 | if ms.LoadMessageInfo() == nil { | 1478 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1242,7 +1485,7 @@ func (x *HeartbeatRequest) ProtoReflect() protoreflect.Message { | |||||
| 1242 | 1485 | ||||
| 1243 | // Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. | 1486 | // Deprecated: Use HeartbeatRequest.ProtoReflect.Descriptor instead. | ||
| 1244 | func (*HeartbeatRequest) Descriptor() ([]byte, []int) { | 1487 | func (*HeartbeatRequest) Descriptor() ([]byte, []int) { | ||
| 1245 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{11} | 1488 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{14} |
| 1246 | } | 1489 | } | ||
| 1247 | 1490 | ||||
| 1248 | func (x *HeartbeatRequest) GetJobId() string { | 1491 | func (x *HeartbeatRequest) GetJobId() string { | ||
| @@ -1261,7 +1504,7 @@ type HeartbeatResponse struct { | |||||
| 1261 | 1504 | ||||
| 1262 | func (x *HeartbeatResponse) Reset() { | 1505 | func (x *HeartbeatResponse) Reset() { | ||
| 1263 | *x = HeartbeatResponse{} | 1506 | *x = HeartbeatResponse{} | ||
| 1264 | - | mi := &file_ermite_runner_proto_msgTypes[12] | 1507 | + | mi := &file_ermite_runner_proto_msgTypes[15] |
| 1265 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1508 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1266 | ms.StoreMessageInfo(mi) | 1509 | ms.StoreMessageInfo(mi) | ||
| 1267 | } | 1510 | } | ||
| @@ -1273,7 +1516,7 @@ func (x *HeartbeatResponse) String() string { | |||||
| 1273 | func (*HeartbeatResponse) ProtoMessage() {} | 1516 | func (*HeartbeatResponse) ProtoMessage() {} | ||
| 1274 | 1517 | ||||
| 1275 | func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { | 1518 | func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { | ||
| 1276 | - | mi := &file_ermite_runner_proto_msgTypes[12] | 1519 | + | mi := &file_ermite_runner_proto_msgTypes[15] |
| 1277 | if x != nil { | 1520 | if x != nil { | ||
| 1278 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1521 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1279 | if ms.LoadMessageInfo() == nil { | 1522 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1286,7 +1529,7 @@ func (x *HeartbeatResponse) ProtoReflect() protoreflect.Message { | |||||
| 1286 | 1529 | ||||
| 1287 | // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. | 1530 | // Deprecated: Use HeartbeatResponse.ProtoReflect.Descriptor instead. | ||
| 1288 | func (*HeartbeatResponse) Descriptor() ([]byte, []int) { | 1531 | func (*HeartbeatResponse) Descriptor() ([]byte, []int) { | ||
| 1289 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{12} | 1532 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{15} |
| 1290 | } | 1533 | } | ||
| 1291 | 1534 | ||||
| 1292 | // WatchJobRequest names the job to watch. | 1535 | // WatchJobRequest names the job to watch. | ||
| @@ -1300,7 +1543,7 @@ type WatchJobRequest struct { | |||||
| 1300 | 1543 | ||||
| 1301 | func (x *WatchJobRequest) Reset() { | 1544 | func (x *WatchJobRequest) Reset() { | ||
| 1302 | *x = WatchJobRequest{} | 1545 | *x = WatchJobRequest{} | ||
| 1303 | - | mi := &file_ermite_runner_proto_msgTypes[13] | 1546 | + | mi := &file_ermite_runner_proto_msgTypes[16] |
| 1304 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1547 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1305 | ms.StoreMessageInfo(mi) | 1548 | ms.StoreMessageInfo(mi) | ||
| 1306 | } | 1549 | } | ||
| @@ -1312,7 +1555,7 @@ func (x *WatchJobRequest) String() string { | |||||
| 1312 | func (*WatchJobRequest) ProtoMessage() {} | 1555 | func (*WatchJobRequest) ProtoMessage() {} | ||
| 1313 | 1556 | ||||
| 1314 | func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { | 1557 | func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { | ||
| 1315 | - | mi := &file_ermite_runner_proto_msgTypes[13] | 1558 | + | mi := &file_ermite_runner_proto_msgTypes[16] |
| 1316 | if x != nil { | 1559 | if x != nil { | ||
| 1317 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1560 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1318 | if ms.LoadMessageInfo() == nil { | 1561 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1325,7 +1568,7 @@ func (x *WatchJobRequest) ProtoReflect() protoreflect.Message { | |||||
| 1325 | 1568 | ||||
| 1326 | // Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead. | 1569 | // Deprecated: Use WatchJobRequest.ProtoReflect.Descriptor instead. | ||
| 1327 | func (*WatchJobRequest) Descriptor() ([]byte, []int) { | 1570 | func (*WatchJobRequest) Descriptor() ([]byte, []int) { | ||
| 1328 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{13} | 1571 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{16} |
| 1329 | } | 1572 | } | ||
| 1330 | 1573 | ||||
| 1331 | func (x *WatchJobRequest) GetJobId() string { | 1574 | func (x *WatchJobRequest) GetJobId() string { | ||
| @@ -1335,28 +1578,25 @@ func (x *WatchJobRequest) GetJobId() string { | |||||
| 1335 | return "" | 1578 | return "" | ||
| 1336 | } | 1579 | } | ||
| 1337 | 1580 | ||||
| 1338 | - | // WatchJobResponse is one message of the stream: a request to cancel the job, | 1581 | + | // WatchJobResponse is one message of the stream. |
| 1339 | - | // or a keep-alive. | |||
| 1340 | type WatchJobResponse struct { | 1582 | type WatchJobResponse struct { | ||
| 1341 | state protoimpl.MessageState `protogen:"open.v1"` | 1583 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| 1342 | - | // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a | 1584 | + | // Msg is what the message carries. New kinds may be added: a client ignores |
| 1343 | - | // keep-alive: the other fields are empty and the client ignores it. New | 1585 | + | // the ones it does not know. |
| 1344 | - | // reasons may be added; a runner that does not know one cancels the job and | 1586 | + | // |
| 1345 | - | // reports CI_CONCLUSION_CANCELLED. | 1587 | + | // Types that are valid to be assigned to Msg: |
| 1346 | - | Reason CancelReason `protobuf:"varint,1,opt,name=reason,proto3,enum=ermite.CancelReason" json:"reason,omitempty"` | 1588 | + | // |
| 1347 | - | // Message is a short human-readable explanation, for the job log, for | 1589 | + | // *WatchJobResponse_Keepalive |
| 1348 | - | // example "Canceled by the owner". At most 1,024 characters. | 1590 | + | // *WatchJobResponse_Cancel |
| 1349 | - | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` | 1591 | + | // *WatchJobResponse_Ended |
| 1350 | - | // RequestedAt is when the cancellation was requested, by the clock of the | 1592 | + | Msg isWatchJobResponse_Msg `protobuf_oneof:"msg"` |
| 1351 | - | // server. | |||
| 1352 | - | RequestedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=requested_at,json=requestedAt,proto3" json:"requested_at,omitempty"` | |||
| 1353 | unknownFields protoimpl.UnknownFields | 1593 | unknownFields protoimpl.UnknownFields | ||
| 1354 | sizeCache protoimpl.SizeCache | 1594 | sizeCache protoimpl.SizeCache | ||
| 1355 | } | 1595 | } | ||
| 1356 | 1596 | ||||
| 1357 | func (x *WatchJobResponse) Reset() { | 1597 | func (x *WatchJobResponse) Reset() { | ||
| 1358 | *x = WatchJobResponse{} | 1598 | *x = WatchJobResponse{} | ||
| 1359 | - | mi := &file_ermite_runner_proto_msgTypes[14] | 1599 | + | mi := &file_ermite_runner_proto_msgTypes[17] |
| 1360 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1600 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1361 | ms.StoreMessageInfo(mi) | 1601 | ms.StoreMessageInfo(mi) | ||
| 1362 | } | 1602 | } | ||
| @@ -1368,7 +1608,7 @@ func (x *WatchJobResponse) String() string { | |||||
| 1368 | func (*WatchJobResponse) ProtoMessage() {} | 1608 | func (*WatchJobResponse) ProtoMessage() {} | ||
| 1369 | 1609 | ||||
| 1370 | func (x *WatchJobResponse) ProtoReflect() protoreflect.Message { | 1610 | func (x *WatchJobResponse) ProtoReflect() protoreflect.Message { | ||
| 1371 | - | mi := &file_ermite_runner_proto_msgTypes[14] | 1611 | + | mi := &file_ermite_runner_proto_msgTypes[17] |
| 1372 | if x != nil { | 1612 | if x != nil { | ||
| 1373 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | 1613 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | ||
| 1374 | if ms.LoadMessageInfo() == nil { | 1614 | if ms.LoadMessageInfo() == nil { | ||
| @@ -1381,24 +1621,167 @@ func (x *WatchJobResponse) ProtoReflect() protoreflect.Message { | |||||
| 1381 | 1621 | ||||
| 1382 | // Deprecated: Use WatchJobResponse.ProtoReflect.Descriptor instead. | 1622 | // Deprecated: Use WatchJobResponse.ProtoReflect.Descriptor instead. | ||
| 1383 | func (*WatchJobResponse) Descriptor() ([]byte, []int) { | 1623 | func (*WatchJobResponse) Descriptor() ([]byte, []int) { | ||
| 1384 | - | return file_ermite_runner_proto_rawDescGZIP(), []int{14} | 1624 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{17} |
| 1385 | } | 1625 | } | ||
| 1386 | 1626 | ||||
| 1387 | - | func (x *WatchJobResponse) GetReason() CancelReason { | 1627 | + | func (x *WatchJobResponse) GetMsg() isWatchJobResponse_Msg { |
| 1628 | + | if x != nil { | |||
| 1629 | + | return x.Msg | |||
| 1630 | + | } | |||
| 1631 | + | return nil | |||
| 1632 | + | } | |||
| 1633 | + | ||||
| 1634 | + | func (x *WatchJobResponse) GetKeepalive() *Keepalive { | |||
| 1635 | + | if x != nil { | |||
| 1636 | + | if x, ok := x.Msg.(*WatchJobResponse_Keepalive); ok { | |||
| 1637 | + | return x.Keepalive | |||
| 1638 | + | } | |||
| 1639 | + | } | |||
| 1640 | + | return nil | |||
| 1641 | + | } | |||
| 1642 | + | ||||
| 1643 | + | func (x *WatchJobResponse) GetCancel() *CancelRequest { | |||
| 1644 | + | if x != nil { | |||
| 1645 | + | if x, ok := x.Msg.(*WatchJobResponse_Cancel); ok { | |||
| 1646 | + | return x.Cancel | |||
| 1647 | + | } | |||
| 1648 | + | } | |||
| 1649 | + | return nil | |||
| 1650 | + | } | |||
| 1651 | + | ||||
| 1652 | + | func (x *WatchJobResponse) GetEnded() *JobEnded { | |||
| 1653 | + | if x != nil { | |||
| 1654 | + | if x, ok := x.Msg.(*WatchJobResponse_Ended); ok { | |||
| 1655 | + | return x.Ended | |||
| 1656 | + | } | |||
| 1657 | + | } | |||
| 1658 | + | return nil | |||
| 1659 | + | } | |||
| 1660 | + | ||||
| 1661 | + | type isWatchJobResponse_Msg interface { | |||
| 1662 | + | isWatchJobResponse_Msg() | |||
| 1663 | + | } | |||
| 1664 | + | ||||
| 1665 | + | type WatchJobResponse_Keepalive struct { | |||
| 1666 | + | // Keepalive: nothing to do, the stream is alive. | |||
| 1667 | + | Keepalive *Keepalive `protobuf:"bytes,1,opt,name=keepalive,proto3,oneof"` | |||
| 1668 | + | } | |||
| 1669 | + | ||||
| 1670 | + | type WatchJobResponse_Cancel struct { | |||
| 1671 | + | // Cancel: the job must stop. | |||
| 1672 | + | Cancel *CancelRequest `protobuf:"bytes,2,opt,name=cancel,proto3,oneof"` | |||
| 1673 | + | } | |||
| 1674 | + | ||||
| 1675 | + | type WatchJobResponse_Ended struct { | |||
| 1676 | + | // Ended: the job has ended. The last message of a stream: a close without | |||
| 1677 | + | // it means the client reconnects. | |||
| 1678 | + | Ended *JobEnded `protobuf:"bytes,3,opt,name=ended,proto3,oneof"` | |||
| 1679 | + | } | |||
| 1680 | + | ||||
| 1681 | + | func (*WatchJobResponse_Keepalive) isWatchJobResponse_Msg() {} | |||
| 1682 | + | ||||
| 1683 | + | func (*WatchJobResponse_Cancel) isWatchJobResponse_Msg() {} | |||
| 1684 | + | ||||
| 1685 | + | func (*WatchJobResponse_Ended) isWatchJobResponse_Msg() {} | |||
| 1686 | + | ||||
| 1687 | + | // Keepalive has no fields. | |||
| 1688 | + | type Keepalive struct { | |||
| 1689 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1690 | + | unknownFields protoimpl.UnknownFields | |||
| 1691 | + | sizeCache protoimpl.SizeCache | |||
| 1692 | + | } | |||
| 1693 | + | ||||
| 1694 | + | func (x *Keepalive) Reset() { | |||
| 1695 | + | *x = Keepalive{} | |||
| 1696 | + | mi := &file_ermite_runner_proto_msgTypes[18] | |||
| 1697 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1698 | + | ms.StoreMessageInfo(mi) | |||
| 1699 | + | } | |||
| 1700 | + | ||||
| 1701 | + | func (x *Keepalive) String() string { | |||
| 1702 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1703 | + | } | |||
| 1704 | + | ||||
| 1705 | + | func (*Keepalive) ProtoMessage() {} | |||
| 1706 | + | ||||
| 1707 | + | func (x *Keepalive) ProtoReflect() protoreflect.Message { | |||
| 1708 | + | mi := &file_ermite_runner_proto_msgTypes[18] | |||
| 1709 | + | if x != nil { | |||
| 1710 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1711 | + | if ms.LoadMessageInfo() == nil { | |||
| 1712 | + | ms.StoreMessageInfo(mi) | |||
| 1713 | + | } | |||
| 1714 | + | return ms | |||
| 1715 | + | } | |||
| 1716 | + | return mi.MessageOf(x) | |||
| 1717 | + | } | |||
| 1718 | + | ||||
| 1719 | + | // Deprecated: Use Keepalive.ProtoReflect.Descriptor instead. | |||
| 1720 | + | func (*Keepalive) Descriptor() ([]byte, []int) { | |||
| 1721 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{18} | |||
| 1722 | + | } | |||
| 1723 | + | ||||
| 1724 | + | // CancelRequest asks the runner to stop a job. | |||
| 1725 | + | type CancelRequest struct { | |||
| 1726 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1727 | + | // Reason is why the job must stop. New reasons may be added; a runner that | |||
| 1728 | + | // does not know one cancels the job and reports CI_CONCLUSION_CANCELLED. | |||
| 1729 | + | Reason CancelReason `protobuf:"varint,1,opt,name=reason,proto3,enum=ermite.CancelReason" json:"reason,omitempty"` | |||
| 1730 | + | // Message is a short human-readable explanation, for the job log, for | |||
| 1731 | + | // example "Canceled by the owner". At most 1,024 characters. | |||
| 1732 | + | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` | |||
| 1733 | + | // RequestedAt is when the cancellation was requested, by the clock of the | |||
| 1734 | + | // server. | |||
| 1735 | + | RequestedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=requested_at,json=requestedAt,proto3" json:"requested_at,omitempty"` | |||
| 1736 | + | unknownFields protoimpl.UnknownFields | |||
| 1737 | + | sizeCache protoimpl.SizeCache | |||
| 1738 | + | } | |||
| 1739 | + | ||||
| 1740 | + | func (x *CancelRequest) Reset() { | |||
| 1741 | + | *x = CancelRequest{} | |||
| 1742 | + | mi := &file_ermite_runner_proto_msgTypes[19] | |||
| 1743 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1744 | + | ms.StoreMessageInfo(mi) | |||
| 1745 | + | } | |||
| 1746 | + | ||||
| 1747 | + | func (x *CancelRequest) String() string { | |||
| 1748 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1749 | + | } | |||
| 1750 | + | ||||
| 1751 | + | func (*CancelRequest) ProtoMessage() {} | |||
| 1752 | + | ||||
| 1753 | + | func (x *CancelRequest) ProtoReflect() protoreflect.Message { | |||
| 1754 | + | mi := &file_ermite_runner_proto_msgTypes[19] | |||
| 1755 | + | if x != nil { | |||
| 1756 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1757 | + | if ms.LoadMessageInfo() == nil { | |||
| 1758 | + | ms.StoreMessageInfo(mi) | |||
| 1759 | + | } | |||
| 1760 | + | return ms | |||
| 1761 | + | } | |||
| 1762 | + | return mi.MessageOf(x) | |||
| 1763 | + | } | |||
| 1764 | + | ||||
| 1765 | + | // Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead. | |||
| 1766 | + | func (*CancelRequest) Descriptor() ([]byte, []int) { | |||
| 1767 | + | return file_ermite_runner_proto_rawDescGZIP(), []int{19} | |||
| 1768 | + | } | |||
| 1769 | + | ||||
| 1770 | + | func (x *CancelRequest) GetReason() CancelReason { | |||
| 1388 | if x != nil { | 1771 | if x != nil { | ||
| 1389 | return x.Reason | 1772 | return x.Reason | ||
| 1390 | } | 1773 | } | ||
| 1391 | return CancelReason_CANCEL_REASON_UNSPECIFIED | 1774 | return CancelReason_CANCEL_REASON_UNSPECIFIED | ||
| 1392 | } | 1775 | } | ||
| 1393 | 1776 | ||||
| 1394 | - | func (x *WatchJobResponse) GetMessage() string { | 1777 | + | func (x *CancelRequest) GetMessage() string { |
| 1395 | if x != nil { | 1778 | if x != nil { | ||
| 1396 | return x.Message | 1779 | return x.Message | ||
| 1397 | } | 1780 | } | ||
| 1398 | return "" | 1781 | return "" | ||
| 1399 | } | 1782 | } | ||
| 1400 | 1783 | ||||
| 1401 | - | func (x *WatchJobResponse) GetRequestedAt() *timestamppb.Timestamp { | 1784 | + | func (x *CancelRequest) GetRequestedAt() *timestamppb.Timestamp { |
| 1402 | if x != nil { | 1785 | if x != nil { | ||
| 1403 | return x.RequestedAt | 1786 | return x.RequestedAt | ||
| 1404 | } | 1787 | } | ||
| @@ -1409,21 +1792,25 @@ var File_ermite_runner_proto protoreflect.FileDescriptor | |||||
| 1409 | 1792 | ||||
| 1410 | const file_ermite_runner_proto_rawDesc = "" + | 1793 | const file_ermite_runner_proto_rawDesc = "" + | ||
| 1411 | "\n" + | 1794 | "\n" + | ||
| 1412 | - | "\x13ermite/runner.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\xb8\x01\n" + | 1795 | + | "\x13ermite/runner.proto\x12\x06ermite\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd2\x01\n" + |
| 1413 | "\x15RegisterRunnerRequest\x12\x12\n" + | 1796 | "\x15RegisterRunnerRequest\x12\x12\n" + | ||
| 1414 | "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + | 1797 | "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + | ||
| 1415 | "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x18\n" + | 1798 | "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x18\n" + | ||
| 1416 | "\aversion\x18\x03 \x01(\tR\aversion\x125\n" + | 1799 | "\aversion\x18\x03 \x01(\tR\aversion\x125\n" + | ||
| 1417 | "\tisolation\x18\x04 \x01(\x0e2\x17.ermite.RunnerIsolationR\tisolation\x12\x12\n" + | 1800 | "\tisolation\x18\x04 \x01(\x0e2\x17.ermite.RunnerIsolationR\tisolation\x12\x12\n" + | ||
| 1418 | "\x04arch\x18\x05 \x01(\tR\x04arch\x12\x0e\n" + | 1801 | "\x04arch\x18\x05 \x01(\tR\x04arch\x12\x0e\n" + | ||
| 1419 | - | "\x02os\x18\x06 \x01(\tR\x02os\"X\n" + | 1802 | + | "\x02os\x18\x06 \x01(\tR\x02os\x12\x18\n" + |
| 1803 | + | "\areplace\x18\a \x01(\bR\areplace\"X\n" + | |||
| 1420 | "\x16RegisterRunnerResponse\x12\x1b\n" + | 1804 | "\x16RegisterRunnerResponse\x12\x1b\n" + | ||
| 1421 | "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" + | 1805 | "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" + | ||
| 1422 | - | "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\")\n" + | 1806 | + | "\frunner_token\x18\x02 \x01(\tR\vrunnerToken\"B\n" + |
| 1423 | "\x0fFetchJobRequest\x12\x16\n" + | 1807 | "\x0fFetchJobRequest\x12\x16\n" + | ||
| 1424 | - | "\x06labels\x18\x01 \x03(\tR\x06labels\"1\n" + | 1808 | + | "\x06labels\x18\x01 \x03(\tR\x06labels\x12\x17\n" + |
| 1809 | + | "\apoll_id\x18\x02 \x01(\tR\x06pollId\"m\n" + | |||
| 1425 | "\x10FetchJobResponse\x12\x1d\n" + | 1810 | "\x10FetchJobResponse\x12\x1d\n" + | ||
| 1426 | - | "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\"\xfa\x02\n" + | 1811 | + | "\x03job\x18\x01 \x01(\v2\v.ermite.JobR\x03job\x12:\n" + |
| 1812 | + | "\vretry_after\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\n" + | |||
| 1813 | + | "retryAfter\"\xfa\x02\n" + | |||
| 1427 | "\x03Job\x12\x15\n" + | 1814 | "\x03Job\x12\x15\n" + | ||
| 1428 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" + | 1815 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x15\n" + | ||
| 1429 | "\x06run_id\x18\x02 \x01(\x03R\x05runId\x12\x18\n" + | 1816 | "\x06run_id\x18\x02 \x01(\x03R\x05runId\x12\x18\n" + | ||
| @@ -1439,18 +1826,10 @@ const file_ermite_runner_proto_rawDesc = "" + | |||||
| 1439 | "\x0ftimeout_minutes\x18\v \x01(\x05R\x0etimeoutMinutes\x12\x12\n" + | 1826 | "\x0ftimeout_minutes\x18\v \x01(\x05R\x0etimeoutMinutes\x12\x12\n" + | ||
| 1440 | "\x04repo\x18\f \x01(\tR\x04repo\x12\x10\n" + | 1827 | "\x04repo\x18\f \x01(\tR\x04repo\x12\x10\n" + | ||
| 1441 | "\x03sha\x18\r \x01(\tR\x03sha\x12\x10\n" + | 1828 | "\x03sha\x18\r \x01(\tR\x03sha\x12\x10\n" + | ||
| 1442 | - | "\x03ref\x18\x0e \x01(\tR\x03ref\"\xaf\x02\n" + | 1829 | + | "\x03ref\x18\x0e \x01(\tR\x03ref\"S\n" + |
| 1443 | "\x10UpdateJobRequest\x12\x15\n" + | 1830 | "\x10UpdateJobRequest\x12\x15\n" + | ||
| 1444 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12(\n" + | 1831 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12(\n" + | ||
| 1445 | - | "\x05steps\x18\x02 \x03(\v2\x12.ermite.StepUpdateR\x05steps\x12?\n" + | 1832 | + | "\x05steps\x18\x02 \x03(\v2\x12.ermite.StepUpdateR\x05steps\"\x91\x02\n" + |
| 1446 | - | "\aoutputs\x18\x03 \x03(\v2%.ermite.UpdateJobRequest.OutputsEntryR\aoutputs\x124\n" + | |||
| 1447 | - | "\n" + | |||
| 1448 | - | "conclusion\x18\x04 \x01(\x0e2\x14.ermite.CiConclusionR\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\"\x91\x02\n" + | |||
| 1454 | "\n" + | 1833 | "\n" + | ||
| 1455 | "StepUpdate\x12\x16\n" + | 1834 | "StepUpdate\x12\x16\n" + | ||
| 1456 | "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" + | 1835 | "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" + | ||
| @@ -1462,15 +1841,38 @@ const file_ermite_runner_proto_rawDesc = "" + | |||||
| 1462 | "\n" + | 1841 | "\n" + | ||
| 1463 | "started_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x12=\n" + | 1842 | "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" + | 1843 | "\fcompleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\vcompletedAt\"\x13\n" + | ||
| 1465 | - | "\x11UpdateJobResponse\"|\n" + | 1844 | + | "\x11UpdateJobResponse\"\xb3\x02\n" + |
| 1845 | + | "\x12CompleteJobRequest\x12\x15\n" + | |||
| 1846 | + | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x124\n" + | |||
| 1847 | + | "\n" + | |||
| 1848 | + | "conclusion\x18\x02 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |||
| 1849 | + | "conclusion\x12A\n" + | |||
| 1850 | + | "\aoutputs\x18\x03 \x03(\v2'.ermite.CompleteJobRequest.OutputsEntryR\aoutputs\x12'\n" + | |||
| 1851 | + | "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x12(\n" + | |||
| 1852 | + | "\x05steps\x18\x05 \x03(\v2\x12.ermite.StepUpdateR\x05steps\x1a:\n" + | |||
| 1853 | + | "\fOutputsEntry\x12\x10\n" + | |||
| 1854 | + | "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + | |||
| 1855 | + | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"{\n" + | |||
| 1856 | + | "\x13CompleteJobResponse\x124\n" + | |||
| 1857 | + | "\n" + | |||
| 1858 | + | "conclusion\x18\x01 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |||
| 1859 | + | "conclusion\x12\x14\n" + | |||
| 1860 | + | "\x05error\x18\x02 \x01(\tR\x05error\x12\x18\n" + | |||
| 1861 | + | "\aapplied\x18\x03 \x01(\bR\aapplied\"V\n" + | |||
| 1862 | + | "\bJobEnded\x124\n" + | |||
| 1863 | + | "\n" + | |||
| 1864 | + | "conclusion\x18\x01 \x01(\x0e2\x14.ermite.CiConclusionR\n" + | |||
| 1865 | + | "conclusion\x12\x14\n" + | |||
| 1866 | + | "\x05error\x18\x02 \x01(\tR\x05error\"|\n" + | |||
| 1466 | "\x10AppendLogRequest\x12\x15\n" + | 1867 | "\x10AppendLogRequest\x12\x15\n" + | ||
| 1467 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x12\n" + | 1868 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\x12\x12\n" + | ||
| 1468 | "\x04step\x18\x02 \x01(\x05R\x04step\x12\x16\n" + | 1869 | "\x04step\x18\x02 \x01(\x05R\x04step\x12\x16\n" + | ||
| 1469 | "\x06offset\x18\x03 \x01(\x03R\x06offset\x12%\n" + | 1870 | "\x06offset\x18\x03 \x01(\x03R\x06offset\x12%\n" + | ||
| 1470 | - | "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"M\n" + | 1871 | + | "\x05lines\x18\x04 \x03(\v2\x0f.ermite.LogLineR\x05lines\"k\n" + |
| 1471 | "\aLogLine\x12.\n" + | 1872 | "\aLogLine\x12.\n" + | ||
| 1472 | "\x04time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x12\x12\n" + | 1873 | "\x04time\x18\x01 \x01(\v2\x1a.google.protobuf.TimestampR\x04time\x12\x12\n" + | ||
| 1473 | - | "\x04text\x18\x02 \x01(\tR\x04text\"4\n" + | 1874 | + | "\x04text\x18\x02 \x01(\tR\x04text\x12\x1c\n" + |
| 1875 | + | "\tcontinued\x18\x03 \x01(\bR\tcontinued\"4\n" + | |||
| 1474 | "\x11AppendLogResponse\x12\x1f\n" + | 1876 | "\x11AppendLogResponse\x12\x1f\n" + | ||
| 1475 | "\vnext_offset\x18\x01 \x01(\x03R\n" + | 1877 | "\vnext_offset\x18\x01 \x01(\x03R\n" + | ||
| 1476 | "nextOffset\")\n" + | 1878 | "nextOffset\")\n" + | ||
| @@ -1478,8 +1880,14 @@ const file_ermite_runner_proto_rawDesc = "" + | |||||
| 1478 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x13\n" + | 1880 | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x13\n" + | ||
| 1479 | "\x11HeartbeatResponse\"(\n" + | 1881 | "\x11HeartbeatResponse\"(\n" + | ||
| 1480 | "\x0fWatchJobRequest\x12\x15\n" + | 1882 | "\x0fWatchJobRequest\x12\x15\n" + | ||
| 1481 | - | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\x99\x01\n" + | 1883 | + | "\x06job_id\x18\x01 \x01(\tR\x05jobId\"\xa7\x01\n" + |
| 1482 | - | "\x10WatchJobResponse\x12,\n" + | 1884 | + | "\x10WatchJobResponse\x121\n" + |
| 1885 | + | "\tkeepalive\x18\x01 \x01(\v2\x11.ermite.KeepaliveH\x00R\tkeepalive\x12/\n" + | |||
| 1886 | + | "\x06cancel\x18\x02 \x01(\v2\x15.ermite.CancelRequestH\x00R\x06cancel\x12(\n" + | |||
| 1887 | + | "\x05ended\x18\x03 \x01(\v2\x10.ermite.JobEndedH\x00R\x05endedB\x05\n" + | |||
| 1888 | + | "\x03msg\"\v\n" + | |||
| 1889 | + | "\tKeepalive\"\x96\x01\n" + | |||
| 1890 | + | "\rCancelRequest\x12,\n" + | |||
| 1483 | "\x06reason\x18\x01 \x01(\x0e2\x14.ermite.CancelReasonR\x06reason\x12\x18\n" + | 1891 | "\x06reason\x18\x01 \x01(\x0e2\x14.ermite.CancelReasonR\x06reason\x12\x18\n" + | ||
| 1484 | "\amessage\x18\x02 \x01(\tR\amessage\x12=\n" + | 1892 | "\amessage\x18\x02 \x01(\tR\amessage\x12=\n" + | ||
| 1485 | "\frequested_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt*o\n" + | 1893 | "\frequested_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\vrequestedAt*o\n" + | ||
| @@ -1511,14 +1919,15 @@ const file_ermite_runner_proto_rawDesc = "" + | |||||
| 1511 | "\x19CANCEL_REASON_UNSPECIFIED\x10\x00\x12\x16\n" + | 1919 | "\x19CANCEL_REASON_UNSPECIFIED\x10\x00\x12\x16\n" + | ||
| 1512 | "\x12CANCEL_REASON_USER\x10\x01\x12\x1b\n" + | 1920 | "\x12CANCEL_REASON_USER\x10\x01\x12\x1b\n" + | ||
| 1513 | "\x17CANCEL_REASON_FAIL_FAST\x10\x02\x12\x19\n" + | 1921 | "\x17CANCEL_REASON_FAIL_FAST\x10\x02\x12\x19\n" + | ||
| 1514 | - | "\x15CANCEL_REASON_TIMEOUT\x10\x032\xa6\x03\n" + | 1922 | + | "\x15CANCEL_REASON_TIMEOUT\x10\x032\xee\x03\n" + |
| 1515 | "\rRunnerService\x12O\n" + | 1923 | "\rRunnerService\x12O\n" + | ||
| 1516 | "\x0eRegisterRunner\x12\x1d.ermite.RegisterRunnerRequest\x1a\x1e.ermite.RegisterRunnerResponse\x12=\n" + | 1924 | "\x0eRegisterRunner\x12\x1d.ermite.RegisterRunnerRequest\x1a\x1e.ermite.RegisterRunnerResponse\x12=\n" + | ||
| 1517 | "\bFetchJob\x12\x17.ermite.FetchJobRequest\x1a\x18.ermite.FetchJobResponse\x12@\n" + | 1925 | "\bFetchJob\x12\x17.ermite.FetchJobRequest\x1a\x18.ermite.FetchJobResponse\x12@\n" + | ||
| 1518 | "\tUpdateJob\x12\x18.ermite.UpdateJobRequest\x1a\x19.ermite.UpdateJobResponse\x12@\n" + | 1926 | "\tUpdateJob\x12\x18.ermite.UpdateJobRequest\x1a\x19.ermite.UpdateJobResponse\x12@\n" + | ||
| 1519 | "\tAppendLog\x12\x18.ermite.AppendLogRequest\x1a\x19.ermite.AppendLogResponse\x12@\n" + | 1927 | "\tAppendLog\x12\x18.ermite.AppendLogRequest\x1a\x19.ermite.AppendLogResponse\x12@\n" + | ||
| 1520 | "\tHeartbeat\x12\x18.ermite.HeartbeatRequest\x1a\x19.ermite.HeartbeatResponse\x12?\n" + | 1928 | "\tHeartbeat\x12\x18.ermite.HeartbeatRequest\x1a\x19.ermite.HeartbeatResponse\x12?\n" + | ||
| 1521 | - | "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01Br\n" + | 1929 | + | "\bWatchJob\x12\x17.ermite.WatchJobRequest\x1a\x18.ermite.WatchJobResponse0\x01\x12F\n" + |
| 1930 | + | "\vCompleteJob\x12\x1a.ermite.CompleteJobRequest\x1a\x1b.ermite.CompleteJobResponseBr\n" + | |||
| 1522 | "\n" + | 1931 | "\n" + | ||
| 1523 | "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" | 1932 | "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" | ||
| 1524 | 1933 | ||||
| @@ -1535,7 +1944,7 @@ func file_ermite_runner_proto_rawDescGZIP() []byte { | |||||
| 1535 | } | 1944 | } | ||
| 1536 | 1945 | ||||
| 1537 | var file_ermite_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 5) | 1946 | var file_ermite_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 5) | ||
| 1538 | - | var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 16) | 1947 | + | var file_ermite_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 21) |
| 1539 | var file_ermite_runner_proto_goTypes = []any{ | 1948 | var file_ermite_runner_proto_goTypes = []any{ | ||
| 1540 | (CiStatus)(0), // 0: ermite.CiStatus | 1949 | (CiStatus)(0), // 0: ermite.CiStatus | ||
| 1541 | (CiConclusion)(0), // 1: ermite.CiConclusion | 1950 | (CiConclusion)(0), // 1: ermite.CiConclusion | ||
| @@ -1550,47 +1959,62 @@ var file_ermite_runner_proto_goTypes = []any{ | |||||
| 1550 | (*UpdateJobRequest)(nil), // 10: ermite.UpdateJobRequest | 1959 | (*UpdateJobRequest)(nil), // 10: ermite.UpdateJobRequest | ||
| 1551 | (*StepUpdate)(nil), // 11: ermite.StepUpdate | 1960 | (*StepUpdate)(nil), // 11: ermite.StepUpdate | ||
| 1552 | (*UpdateJobResponse)(nil), // 12: ermite.UpdateJobResponse | 1961 | (*UpdateJobResponse)(nil), // 12: ermite.UpdateJobResponse | ||
| 1553 | - | (*AppendLogRequest)(nil), // 13: ermite.AppendLogRequest | 1962 | + | (*CompleteJobRequest)(nil), // 13: ermite.CompleteJobRequest |
| 1554 | - | (*LogLine)(nil), // 14: ermite.LogLine | 1963 | + | (*CompleteJobResponse)(nil), // 14: ermite.CompleteJobResponse |
| 1555 | - | (*AppendLogResponse)(nil), // 15: ermite.AppendLogResponse | 1964 | + | (*JobEnded)(nil), // 15: ermite.JobEnded |
| 1556 | - | (*HeartbeatRequest)(nil), // 16: ermite.HeartbeatRequest | 1965 | + | (*AppendLogRequest)(nil), // 16: ermite.AppendLogRequest |
| 1557 | - | (*HeartbeatResponse)(nil), // 17: ermite.HeartbeatResponse | 1966 | + | (*LogLine)(nil), // 17: ermite.LogLine |
| 1558 | - | (*WatchJobRequest)(nil), // 18: ermite.WatchJobRequest | 1967 | + | (*AppendLogResponse)(nil), // 18: ermite.AppendLogResponse |
| 1559 | - | (*WatchJobResponse)(nil), // 19: ermite.WatchJobResponse | 1968 | + | (*HeartbeatRequest)(nil), // 19: ermite.HeartbeatRequest |
| 1560 | - | nil, // 20: ermite.UpdateJobRequest.OutputsEntry | 1969 | + | (*HeartbeatResponse)(nil), // 20: ermite.HeartbeatResponse |
| 1561 | - | (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp | 1970 | + | (*WatchJobRequest)(nil), // 21: ermite.WatchJobRequest |
| 1971 | + | (*WatchJobResponse)(nil), // 22: ermite.WatchJobResponse | |||
| 1972 | + | (*Keepalive)(nil), // 23: ermite.Keepalive | |||
| 1973 | + | (*CancelRequest)(nil), // 24: ermite.CancelRequest | |||
| 1974 | + | nil, // 25: ermite.CompleteJobRequest.OutputsEntry | |||
| 1975 | + | (*durationpb.Duration)(nil), // 26: google.protobuf.Duration | |||
| 1976 | + | (*timestamppb.Timestamp)(nil), // 27: google.protobuf.Timestamp | |||
| 1562 | } | 1977 | } | ||
| 1563 | var file_ermite_runner_proto_depIdxs = []int32{ | 1978 | var file_ermite_runner_proto_depIdxs = []int32{ | ||
| 1564 | 3, // 0: ermite.RegisterRunnerRequest.isolation:type_name -> ermite.RunnerIsolation | 1979 | 3, // 0: ermite.RegisterRunnerRequest.isolation:type_name -> ermite.RunnerIsolation | ||
| 1565 | 9, // 1: ermite.FetchJobResponse.job:type_name -> ermite.Job | 1980 | 9, // 1: ermite.FetchJobResponse.job:type_name -> ermite.Job | ||
| 1566 | - | 11, // 2: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate | 1981 | + | 26, // 2: ermite.FetchJobResponse.retry_after:type_name -> google.protobuf.Duration |
| 1567 | - | 20, // 3: ermite.UpdateJobRequest.outputs:type_name -> ermite.UpdateJobRequest.OutputsEntry | 1982 | + | 11, // 3: ermite.UpdateJobRequest.steps:type_name -> ermite.StepUpdate |
| 1568 | - | 1, // 4: ermite.UpdateJobRequest.conclusion:type_name -> ermite.CiConclusion | 1983 | + | 2, // 4: ermite.StepUpdate.state:type_name -> ermite.StepState |
| 1569 | - | 2, // 5: ermite.StepUpdate.state:type_name -> ermite.StepState | 1984 | + | 1, // 5: ermite.StepUpdate.conclusion:type_name -> ermite.CiConclusion |
| 1570 | - | 1, // 6: ermite.StepUpdate.conclusion:type_name -> ermite.CiConclusion | 1985 | + | 27, // 6: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp |
| 1571 | - | 21, // 7: ermite.StepUpdate.started_at:type_name -> google.protobuf.Timestamp | 1986 | + | 27, // 7: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp |
| 1572 | - | 21, // 8: ermite.StepUpdate.completed_at:type_name -> google.protobuf.Timestamp | 1987 | + | 1, // 8: ermite.CompleteJobRequest.conclusion:type_name -> ermite.CiConclusion |
| 1573 | - | 14, // 9: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine | 1988 | + | 25, // 9: ermite.CompleteJobRequest.outputs:type_name -> ermite.CompleteJobRequest.OutputsEntry |
| 1574 | - | 21, // 10: ermite.LogLine.time:type_name -> google.protobuf.Timestamp | 1989 | + | 11, // 10: ermite.CompleteJobRequest.steps:type_name -> ermite.StepUpdate |
| 1575 | - | 4, // 11: ermite.WatchJobResponse.reason:type_name -> ermite.CancelReason | 1990 | + | 1, // 11: ermite.CompleteJobResponse.conclusion:type_name -> ermite.CiConclusion |
| 1576 | - | 21, // 12: ermite.WatchJobResponse.requested_at:type_name -> google.protobuf.Timestamp | 1991 | + | 1, // 12: ermite.JobEnded.conclusion:type_name -> ermite.CiConclusion |
| 1577 | - | 5, // 13: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest | 1992 | + | 17, // 13: ermite.AppendLogRequest.lines:type_name -> ermite.LogLine |
| 1578 | - | 7, // 14: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest | 1993 | + | 27, // 14: ermite.LogLine.time:type_name -> google.protobuf.Timestamp |
| 1579 | - | 10, // 15: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest | 1994 | + | 23, // 15: ermite.WatchJobResponse.keepalive:type_name -> ermite.Keepalive |
| 1580 | - | 13, // 16: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest | 1995 | + | 24, // 16: ermite.WatchJobResponse.cancel:type_name -> ermite.CancelRequest |
| 1581 | - | 16, // 17: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest | 1996 | + | 15, // 17: ermite.WatchJobResponse.ended:type_name -> ermite.JobEnded |
| 1582 | - | 18, // 18: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest | 1997 | + | 4, // 18: ermite.CancelRequest.reason:type_name -> ermite.CancelReason |
| 1583 | - | 6, // 19: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse | 1998 | + | 27, // 19: ermite.CancelRequest.requested_at:type_name -> google.protobuf.Timestamp |
| 1584 | - | 8, // 20: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse | 1999 | + | 5, // 20: ermite.RunnerService.RegisterRunner:input_type -> ermite.RegisterRunnerRequest |
| 1585 | - | 12, // 21: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse | 2000 | + | 7, // 21: ermite.RunnerService.FetchJob:input_type -> ermite.FetchJobRequest |
| 1586 | - | 15, // 22: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse | 2001 | + | 10, // 22: ermite.RunnerService.UpdateJob:input_type -> ermite.UpdateJobRequest |
| 1587 | - | 17, // 23: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse | 2002 | + | 16, // 23: ermite.RunnerService.AppendLog:input_type -> ermite.AppendLogRequest |
| 1588 | - | 19, // 24: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse | 2003 | + | 19, // 24: ermite.RunnerService.Heartbeat:input_type -> ermite.HeartbeatRequest |
| 1589 | - | 19, // [19:25] is the sub-list for method output_type | 2004 | + | 21, // 25: ermite.RunnerService.WatchJob:input_type -> ermite.WatchJobRequest |
| 1590 | - | 13, // [13:19] is the sub-list for method input_type | 2005 | + | 13, // 26: ermite.RunnerService.CompleteJob:input_type -> ermite.CompleteJobRequest |
| 1591 | - | 13, // [13:13] is the sub-list for extension type_name | 2006 | + | 6, // 27: ermite.RunnerService.RegisterRunner:output_type -> ermite.RegisterRunnerResponse |
| 1592 | - | 13, // [13:13] is the sub-list for extension extendee | 2007 | + | 8, // 28: ermite.RunnerService.FetchJob:output_type -> ermite.FetchJobResponse |
| 1593 | - | 0, // [0:13] is the sub-list for field type_name | 2008 | + | 12, // 29: ermite.RunnerService.UpdateJob:output_type -> ermite.UpdateJobResponse |
| 2009 | + | 18, // 30: ermite.RunnerService.AppendLog:output_type -> ermite.AppendLogResponse | |||
| 2010 | + | 20, // 31: ermite.RunnerService.Heartbeat:output_type -> ermite.HeartbeatResponse | |||
| 2011 | + | 22, // 32: ermite.RunnerService.WatchJob:output_type -> ermite.WatchJobResponse | |||
| 2012 | + | 14, // 33: ermite.RunnerService.CompleteJob:output_type -> ermite.CompleteJobResponse | |||
| 2013 | + | 27, // [27:34] is the sub-list for method output_type | |||
| 2014 | + | 20, // [20:27] is the sub-list for method input_type | |||
| 2015 | + | 20, // [20:20] is the sub-list for extension type_name | |||
| 2016 | + | 20, // [20:20] is the sub-list for extension extendee | |||
| 2017 | + | 0, // [0:20] is the sub-list for field type_name | |||
| 1594 | } | 2018 | } | ||
| 1595 | 2019 | ||||
| 1596 | func init() { file_ermite_runner_proto_init() } | 2020 | func init() { file_ermite_runner_proto_init() } | ||
| @@ -1598,13 +2022,18 @@ func file_ermite_runner_proto_init() { | |||||
| 1598 | if File_ermite_runner_proto != nil { | 2022 | if File_ermite_runner_proto != nil { | ||
| 1599 | return | 2023 | return | ||
| 1600 | } | 2024 | } | ||
| 2025 | + | file_ermite_runner_proto_msgTypes[17].OneofWrappers = []any{ | |||
| 2026 | + | (*WatchJobResponse_Keepalive)(nil), | |||
| 2027 | + | (*WatchJobResponse_Cancel)(nil), | |||
| 2028 | + | (*WatchJobResponse_Ended)(nil), | |||
| 2029 | + | } | |||
| 1601 | type x struct{} | 2030 | type x struct{} | ||
| 1602 | out := protoimpl.TypeBuilder{ | 2031 | out := protoimpl.TypeBuilder{ | ||
| 1603 | File: protoimpl.DescBuilder{ | 2032 | File: protoimpl.DescBuilder{ | ||
| 1604 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | 2033 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||
| 1605 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)), | 2034 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_runner_proto_rawDesc), len(file_ermite_runner_proto_rawDesc)), | ||
| 1606 | NumEnums: 5, | 2035 | NumEnums: 5, | ||
| 1607 | - | NumMessages: 16, | 2036 | + | NumMessages: 21, |
| 1608 | NumExtensions: 0, | 2037 | NumExtensions: 0, | ||
| 1609 | NumServices: 1, | 2038 | NumServices: 1, | ||
| 1610 | }, | 2039 | }, | ||
M gen_test.go +105 -14
| @@ -3,8 +3,11 @@ | |||
| 3 | 3 | package api_test | |
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | + | "errors" | |
| 6 | 7 | "testing" | |
| 7 | 8 | ||
| 9 | + | "connectrpc.com/connect" | |
| 10 | + | ||
| 8 | 11 | "google.golang.org/protobuf/proto" | |
| 9 | 12 | "google.golang.org/protobuf/reflect/protoreflect" | |
| 10 | 13 | "google.golang.org/protobuf/types/known/fieldmaskpb" | |
| @@ -50,6 +53,7 @@ func TestProcedures(t *testing.T) { | |||
| 50 | 53 | {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"}, | |
| 51 | 54 | {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"}, | |
| 52 | 55 | {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"}, | |
| 56 | + | {ermiteconnect.RunnerServiceCompleteJobProcedure, "/ermite.RunnerService/CompleteJob"}, | |
| 53 | 57 | {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"}, | |
| 54 | 58 | {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"}, | |
| 55 | 59 | {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"}, | |
| @@ -87,20 +91,40 @@ func TestEnumNumbers(t *testing.T) { | |||
| 87 | 91 | if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 { | |
| 88 | 92 | t.Errorf("DIFF_SIDE_NEW = %d, want 2", got) | |
| 89 | 93 | } | |
| 90 | - | if got := ermite.CiStatus_CI_STATUS_COMPLETED; got != 3 { | |
| 91 | - | t.Errorf("RUN_STATUS_COMPLETED = %d, want 3", got) | |
| 92 | - | } | |
| 93 | - | if got := ermite.CiConclusion_CI_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) | |
| 94 | + | enums := []struct { | |
| 95 | + | name string | |
| 96 | + | got int32 | |
| 97 | + | want int32 | |
| 98 | + | }{ | |
| 99 | + | {"CI_STATUS_UNSPECIFIED", int32(ermite.CiStatus_CI_STATUS_UNSPECIFIED), 0}, | |
| 100 | + | {"CI_STATUS_QUEUED", int32(ermite.CiStatus_CI_STATUS_QUEUED), 1}, | |
| 101 | + | {"CI_STATUS_IN_PROGRESS", int32(ermite.CiStatus_CI_STATUS_IN_PROGRESS), 2}, | |
| 102 | + | {"CI_STATUS_COMPLETED", int32(ermite.CiStatus_CI_STATUS_COMPLETED), 3}, | |
| 103 | + | {"CI_CONCLUSION_UNSPECIFIED", int32(ermite.CiConclusion_CI_CONCLUSION_UNSPECIFIED), 0}, | |
| 104 | + | {"CI_CONCLUSION_SUCCESS", int32(ermite.CiConclusion_CI_CONCLUSION_SUCCESS), 1}, | |
| 105 | + | {"CI_CONCLUSION_FAILURE", int32(ermite.CiConclusion_CI_CONCLUSION_FAILURE), 2}, | |
| 106 | + | {"CI_CONCLUSION_CANCELLED", int32(ermite.CiConclusion_CI_CONCLUSION_CANCELLED), 3}, | |
| 107 | + | {"CI_CONCLUSION_SKIPPED", int32(ermite.CiConclusion_CI_CONCLUSION_SKIPPED), 4}, | |
| 108 | + | {"CI_CONCLUSION_TIMED_OUT", int32(ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT), 5}, | |
| 109 | + | {"CI_CONCLUSION_NEUTRAL", int32(ermite.CiConclusion_CI_CONCLUSION_NEUTRAL), 6}, | |
| 110 | + | {"CI_CONCLUSION_ACTION_REQUIRED", int32(ermite.CiConclusion_CI_CONCLUSION_ACTION_REQUIRED), 7}, | |
| 111 | + | {"STEP_STATE_UNSPECIFIED", int32(ermite.StepState_STEP_STATE_UNSPECIFIED), 0}, | |
| 112 | + | {"STEP_STATE_PENDING", int32(ermite.StepState_STEP_STATE_PENDING), 1}, | |
| 113 | + | {"STEP_STATE_IN_PROGRESS", int32(ermite.StepState_STEP_STATE_IN_PROGRESS), 2}, | |
| 114 | + | {"STEP_STATE_COMPLETED", int32(ermite.StepState_STEP_STATE_COMPLETED), 3}, | |
| 115 | + | {"RUNNER_ISOLATION_UNSPECIFIED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED), 0}, | |
| 116 | + | {"RUNNER_ISOLATION_VM", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_VM), 1}, | |
| 117 | + | {"RUNNER_ISOLATION_CONTAINER_SYSBOX", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_SYSBOX), 2}, | |
| 118 | + | {"RUNNER_ISOLATION_CONTAINER_PRIVILEGED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED), 3}, | |
| 119 | + | {"CANCEL_REASON_UNSPECIFIED", int32(ermite.CancelReason_CANCEL_REASON_UNSPECIFIED), 0}, | |
| 120 | + | {"CANCEL_REASON_USER", int32(ermite.CancelReason_CANCEL_REASON_USER), 1}, | |
| 121 | + | {"CANCEL_REASON_FAIL_FAST", int32(ermite.CancelReason_CANCEL_REASON_FAIL_FAST), 2}, | |
| 122 | + | {"CANCEL_REASON_TIMEOUT", int32(ermite.CancelReason_CANCEL_REASON_TIMEOUT), 3}, | |
| 123 | + | } | |
| 124 | + | for _, e := range enums { | |
| 125 | + | if e.got != e.want { | |
| 126 | + | t.Errorf("%s = %d, want %d", e.name, e.got, e.want) | |
| 127 | + | } | |
| 104 | 128 | } | |
| 105 | 129 | } | |
| 106 | 130 | ||
| @@ -238,3 +262,70 @@ func TestJobRoundTrip(t *testing.T) { | |||
| 238 | 262 | t.Errorf("round trip mismatch: %v != %v", &out, in) | |
| 239 | 263 | } | |
| 240 | 264 | } | |
| 265 | + | ||
| 266 | + | // TestWatchJobRoundTrip checks the three kinds of WatchJob message and the | |
| 267 | + | // JobEnded detail. | |
| 268 | + | func TestWatchJobRoundTrip(t *testing.T) { | |
| 269 | + | msgs := []*ermite.WatchJobResponse{ | |
| 270 | + | {Msg: &ermite.WatchJobResponse_Keepalive{Keepalive: &ermite.Keepalive{}}}, | |
| 271 | + | {Msg: &ermite.WatchJobResponse_Cancel{Cancel: &ermite.CancelRequest{ | |
| 272 | + | Reason: ermite.CancelReason_CANCEL_REASON_FAIL_FAST, Message: "m", | |
| 273 | + | }}}, | |
| 274 | + | {Msg: &ermite.WatchJobResponse_Ended{Ended: &ermite.JobEnded{ | |
| 275 | + | Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED, Error: "e", | |
| 276 | + | }}}, | |
| 277 | + | } | |
| 278 | + | for _, in := range msgs { | |
| 279 | + | b, err := proto.Marshal(in) | |
| 280 | + | if err != nil { | |
| 281 | + | t.Fatal(err) | |
| 282 | + | } | |
| 283 | + | var out ermite.WatchJobResponse | |
| 284 | + | if err := proto.Unmarshal(b, &out); err != nil { | |
| 285 | + | t.Fatal(err) | |
| 286 | + | } | |
| 287 | + | if !proto.Equal(in, &out) { | |
| 288 | + | t.Errorf("round trip mismatch: %v != %v", &out, in) | |
| 289 | + | } | |
| 290 | + | } | |
| 291 | + | // The detail travels in a Connect error. | |
| 292 | + | d, err := connect.NewErrorDetail(&ermite.JobEnded{Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT}) | |
| 293 | + | if err != nil { | |
| 294 | + | t.Fatal(err) | |
| 295 | + | } | |
| 296 | + | cerr := connect.NewError(connect.CodeFailedPrecondition, errors.New("job ended")) | |
| 297 | + | cerr.AddDetail(d) | |
| 298 | + | v, err := cerr.Details()[0].Value() | |
| 299 | + | if err != nil { | |
| 300 | + | t.Fatal(err) | |
| 301 | + | } | |
| 302 | + | if got := v.(*ermite.JobEnded).GetConclusion(); got != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT { | |
| 303 | + | t.Errorf("detail conclusion = %v", got) | |
| 304 | + | } | |
| 305 | + | } | |
| 306 | + | ||
| 307 | + | // TestCompleteJobRoundTrip checks the final report of a job and its answer. | |
| 308 | + | func TestCompleteJobRoundTrip(t *testing.T) { | |
| 309 | + | in := &ermite.CompleteJobRequest{ | |
| 310 | + | JobId: "j1", | |
| 311 | + | Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED, | |
| 312 | + | Outputs: map[string]string{"a": "b"}, | |
| 313 | + | RunnerShutdown: true, | |
| 314 | + | Steps: []*ermite.StepUpdate{{Number: 2, State: ermite.StepState_STEP_STATE_COMPLETED}}, | |
| 315 | + | } | |
| 316 | + | b, err := proto.Marshal(in) | |
| 317 | + | if err != nil { | |
| 318 | + | t.Fatal(err) | |
| 319 | + | } | |
| 320 | + | var out ermite.CompleteJobRequest | |
| 321 | + | if err := proto.Unmarshal(b, &out); err != nil { | |
| 322 | + | t.Fatal(err) | |
| 323 | + | } | |
| 324 | + | if !proto.Equal(in, &out) { | |
| 325 | + | t.Errorf("round trip mismatch: %v != %v", &out, in) | |
| 326 | + | } | |
| 327 | + | r := &ermite.CompleteJobResponse{Applied: false, Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT} | |
| 328 | + | if r.GetApplied() || r.GetConclusion() != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT { | |
| 329 | + | t.Errorf("unexpected response %v", r) | |
| 330 | + | } | |
| 331 | + | } | |
| @@ -3,8 +3,11 @@ | |||||
| 3 | package api_test | 3 | package api_test | ||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | + | "errors" | |||
| 6 | "testing" | 7 | "testing" | ||
| 7 | 8 | ||||
| 9 | + | "connectrpc.com/connect" | |||
| 10 | + | ||||
| 8 | "google.golang.org/protobuf/proto" | 11 | "google.golang.org/protobuf/proto" | ||
| 9 | "google.golang.org/protobuf/reflect/protoreflect" | 12 | "google.golang.org/protobuf/reflect/protoreflect" | ||
| 10 | "google.golang.org/protobuf/types/known/fieldmaskpb" | 13 | "google.golang.org/protobuf/types/known/fieldmaskpb" | ||
| @@ -50,6 +53,7 @@ func TestProcedures(t *testing.T) { | |||||
| 50 | {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"}, | 53 | {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"}, | ||
| 51 | {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"}, | 54 | {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"}, | ||
| 52 | {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"}, | 55 | {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"}, | ||
| 56 | + | {ermiteconnect.RunnerServiceCompleteJobProcedure, "/ermite.RunnerService/CompleteJob"}, | |||
| 53 | {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"}, | 57 | {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"}, | ||
| 54 | {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"}, | 58 | {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"}, | ||
| 55 | {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"}, | 59 | {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"}, | ||
| @@ -87,20 +91,40 @@ func TestEnumNumbers(t *testing.T) { | |||||
| 87 | if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 { | 91 | if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 { | ||
| 88 | t.Errorf("DIFF_SIDE_NEW = %d, want 2", got) | 92 | t.Errorf("DIFF_SIDE_NEW = %d, want 2", got) | ||
| 89 | } | 93 | } | ||
| 90 | - | if got := ermite.CiStatus_CI_STATUS_COMPLETED; got != 3 { | 94 | + | enums := []struct { |
| 91 | - | t.Errorf("RUN_STATUS_COMPLETED = %d, want 3", got) | 95 | + | name string |
| 92 | - | } | 96 | + | got int32 |
| 93 | - | if got := ermite.CiConclusion_CI_CONCLUSION_ACTION_REQUIRED; got != 7 { | 97 | + | want int32 |
| 94 | - | t.Errorf("CONCLUSION_ACTION_REQUIRED = %d, want 7", got) | 98 | + | }{ |
| 95 | - | } | 99 | + | {"CI_STATUS_UNSPECIFIED", int32(ermite.CiStatus_CI_STATUS_UNSPECIFIED), 0}, |
| 96 | - | if got := ermite.StepState_STEP_STATE_COMPLETED; got != 3 { | 100 | + | {"CI_STATUS_QUEUED", int32(ermite.CiStatus_CI_STATUS_QUEUED), 1}, |
| 97 | - | t.Errorf("STEP_STATE_COMPLETED = %d, want 3", got) | 101 | + | {"CI_STATUS_IN_PROGRESS", int32(ermite.CiStatus_CI_STATUS_IN_PROGRESS), 2}, |
| 98 | - | } | 102 | + | {"CI_STATUS_COMPLETED", int32(ermite.CiStatus_CI_STATUS_COMPLETED), 3}, |
| 99 | - | if got := ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED; got != 3 { | 103 | + | {"CI_CONCLUSION_UNSPECIFIED", int32(ermite.CiConclusion_CI_CONCLUSION_UNSPECIFIED), 0}, |
| 100 | - | t.Errorf("RUNNER_ISOLATION_CONTAINER_PRIVILEGED = %d, want 3", got) | 104 | + | {"CI_CONCLUSION_SUCCESS", int32(ermite.CiConclusion_CI_CONCLUSION_SUCCESS), 1}, |
| 101 | - | } | 105 | + | {"CI_CONCLUSION_FAILURE", int32(ermite.CiConclusion_CI_CONCLUSION_FAILURE), 2}, |
| 102 | - | if got := ermite.CancelReason_CANCEL_REASON_TIMEOUT; got != 3 { | 106 | + | {"CI_CONCLUSION_CANCELLED", int32(ermite.CiConclusion_CI_CONCLUSION_CANCELLED), 3}, |
| 103 | - | t.Errorf("CANCEL_REASON_TIMEOUT = %d, want 3", got) | 107 | + | {"CI_CONCLUSION_SKIPPED", int32(ermite.CiConclusion_CI_CONCLUSION_SKIPPED), 4}, |
| 108 | + | {"CI_CONCLUSION_TIMED_OUT", int32(ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT), 5}, | |||
| 109 | + | {"CI_CONCLUSION_NEUTRAL", int32(ermite.CiConclusion_CI_CONCLUSION_NEUTRAL), 6}, | |||
| 110 | + | {"CI_CONCLUSION_ACTION_REQUIRED", int32(ermite.CiConclusion_CI_CONCLUSION_ACTION_REQUIRED), 7}, | |||
| 111 | + | {"STEP_STATE_UNSPECIFIED", int32(ermite.StepState_STEP_STATE_UNSPECIFIED), 0}, | |||
| 112 | + | {"STEP_STATE_PENDING", int32(ermite.StepState_STEP_STATE_PENDING), 1}, | |||
| 113 | + | {"STEP_STATE_IN_PROGRESS", int32(ermite.StepState_STEP_STATE_IN_PROGRESS), 2}, | |||
| 114 | + | {"STEP_STATE_COMPLETED", int32(ermite.StepState_STEP_STATE_COMPLETED), 3}, | |||
| 115 | + | {"RUNNER_ISOLATION_UNSPECIFIED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED), 0}, | |||
| 116 | + | {"RUNNER_ISOLATION_VM", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_VM), 1}, | |||
| 117 | + | {"RUNNER_ISOLATION_CONTAINER_SYSBOX", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_SYSBOX), 2}, | |||
| 118 | + | {"RUNNER_ISOLATION_CONTAINER_PRIVILEGED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED), 3}, | |||
| 119 | + | {"CANCEL_REASON_UNSPECIFIED", int32(ermite.CancelReason_CANCEL_REASON_UNSPECIFIED), 0}, | |||
| 120 | + | {"CANCEL_REASON_USER", int32(ermite.CancelReason_CANCEL_REASON_USER), 1}, | |||
| 121 | + | {"CANCEL_REASON_FAIL_FAST", int32(ermite.CancelReason_CANCEL_REASON_FAIL_FAST), 2}, | |||
| 122 | + | {"CANCEL_REASON_TIMEOUT", int32(ermite.CancelReason_CANCEL_REASON_TIMEOUT), 3}, | |||
| 123 | + | } | |||
| 124 | + | for _, e := range enums { | |||
| 125 | + | if e.got != e.want { | |||
| 126 | + | t.Errorf("%s = %d, want %d", e.name, e.got, e.want) | |||
| 127 | + | } | |||
| 104 | } | 128 | } | ||
| 105 | } | 129 | } | ||
| 106 | 130 | ||||
| @@ -238,3 +262,70 @@ func TestJobRoundTrip(t *testing.T) { | |||||
| 238 | t.Errorf("round trip mismatch: %v != %v", &out, in) | 262 | t.Errorf("round trip mismatch: %v != %v", &out, in) | ||
| 239 | } | 263 | } | ||
| 240 | } | 264 | } | ||
| 265 | + | ||||
| 266 | + | // TestWatchJobRoundTrip checks the three kinds of WatchJob message and the | |||
| 267 | + | // JobEnded detail. | |||
| 268 | + | func TestWatchJobRoundTrip(t *testing.T) { | |||
| 269 | + | msgs := []*ermite.WatchJobResponse{ | |||
| 270 | + | {Msg: &ermite.WatchJobResponse_Keepalive{Keepalive: &ermite.Keepalive{}}}, | |||
| 271 | + | {Msg: &ermite.WatchJobResponse_Cancel{Cancel: &ermite.CancelRequest{ | |||
| 272 | + | Reason: ermite.CancelReason_CANCEL_REASON_FAIL_FAST, Message: "m", | |||
| 273 | + | }}}, | |||
| 274 | + | {Msg: &ermite.WatchJobResponse_Ended{Ended: &ermite.JobEnded{ | |||
| 275 | + | Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED, Error: "e", | |||
| 276 | + | }}}, | |||
| 277 | + | } | |||
| 278 | + | for _, in := range msgs { | |||
| 279 | + | b, err := proto.Marshal(in) | |||
| 280 | + | if err != nil { | |||
| 281 | + | t.Fatal(err) | |||
| 282 | + | } | |||
| 283 | + | var out ermite.WatchJobResponse | |||
| 284 | + | if err := proto.Unmarshal(b, &out); err != nil { | |||
| 285 | + | t.Fatal(err) | |||
| 286 | + | } | |||
| 287 | + | if !proto.Equal(in, &out) { | |||
| 288 | + | t.Errorf("round trip mismatch: %v != %v", &out, in) | |||
| 289 | + | } | |||
| 290 | + | } | |||
| 291 | + | // The detail travels in a Connect error. | |||
| 292 | + | d, err := connect.NewErrorDetail(&ermite.JobEnded{Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT}) | |||
| 293 | + | if err != nil { | |||
| 294 | + | t.Fatal(err) | |||
| 295 | + | } | |||
| 296 | + | cerr := connect.NewError(connect.CodeFailedPrecondition, errors.New("job ended")) | |||
| 297 | + | cerr.AddDetail(d) | |||
| 298 | + | v, err := cerr.Details()[0].Value() | |||
| 299 | + | if err != nil { | |||
| 300 | + | t.Fatal(err) | |||
| 301 | + | } | |||
| 302 | + | if got := v.(*ermite.JobEnded).GetConclusion(); got != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT { | |||
| 303 | + | t.Errorf("detail conclusion = %v", got) | |||
| 304 | + | } | |||
| 305 | + | } | |||
| 306 | + | ||||
| 307 | + | // TestCompleteJobRoundTrip checks the final report of a job and its answer. | |||
| 308 | + | func TestCompleteJobRoundTrip(t *testing.T) { | |||
| 309 | + | in := &ermite.CompleteJobRequest{ | |||
| 310 | + | JobId: "j1", | |||
| 311 | + | Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED, | |||
| 312 | + | Outputs: map[string]string{"a": "b"}, | |||
| 313 | + | RunnerShutdown: true, | |||
| 314 | + | Steps: []*ermite.StepUpdate{{Number: 2, State: ermite.StepState_STEP_STATE_COMPLETED}}, | |||
| 315 | + | } | |||
| 316 | + | b, err := proto.Marshal(in) | |||
| 317 | + | if err != nil { | |||
| 318 | + | t.Fatal(err) | |||
| 319 | + | } | |||
| 320 | + | var out ermite.CompleteJobRequest | |||
| 321 | + | if err := proto.Unmarshal(b, &out); err != nil { | |||
| 322 | + | t.Fatal(err) | |||
| 323 | + | } | |||
| 324 | + | if !proto.Equal(in, &out) { | |||
| 325 | + | t.Errorf("round trip mismatch: %v != %v", &out, in) | |||
| 326 | + | } | |||
| 327 | + | r := &ermite.CompleteJobResponse{Applied: false, Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT} | |||
| 328 | + | if r.GetApplied() || r.GetConclusion() != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT { | |||
| 329 | + | t.Errorf("unexpected response %v", r) | |||
| 330 | + | } | |||
| 331 | + | } | |||
M proto/ermite/event.proto +5 -3
| @@ -26,8 +26,10 @@ service EventService { | |||
| 26 | 26 | // "home"), subject to the same visibility. They carry the state after the | |
| 27 | 27 | // change and are published when a run or a job is created, changes status | |
| 28 | 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 | |
| 29 | + | // line: follow a log with RunService.GetJobLog). The order of these events | |
| 30 | + | // is not guaranteed, between jobs and runs or between two updates of the same | |
| 31 | + | // one: a client keeps the copy with the higher Run.version or | |
| 32 | + | // RunJob.version and drops the other. There is no event for a change of title or | |
| 31 | 33 | // description, nor for a resolved comment: after a gap, or to see such | |
| 32 | 34 | // changes, clients read the merge request again. A merge publishes | |
| 33 | 35 | // merge_request_merged, then pushed for the target branch. | |
| @@ -171,7 +173,7 @@ message CommentAdded { | |||
| 171 | 173 | // Gap tells the client that events were lost: the requested resume point is | |
| 172 | 174 | // older than the server's buffer. It has no fields. The client should reload | |
| 173 | 175 | // the state it displays (repositories, refs, merge requests and their | |
| 174 | - | // comments) and carry on with the next events. | |
| 176 | + | // comments, CI runs and their jobs) and carry on with the next events. | |
| 175 | 177 | message Gap {} | |
| 176 | 178 | ||
| 177 | 179 | // RunUpdated reports a CI run that was created or whose status, conclusion or | |
| @@ -26,8 +26,10 @@ service EventService { | |||||
| 26 | // "home"), subject to the same visibility. They carry the state after the | 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 | 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 | 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 | 29 | + | // line: follow a log with RunService.GetJobLog). The order of these events |
| 30 | - | // before the update of its run. There is no event for a change of title or | 30 | + | // is not guaranteed, between jobs and runs or between two updates of the same |
| 31 | + | // one: a client keeps the copy with the higher Run.version or | |||
| 32 | + | // RunJob.version and drops the other. There is no event for a change of title or | |||
| 31 | // description, nor for a resolved comment: after a gap, or to see such | 33 | // description, nor for a resolved comment: after a gap, or to see such | ||
| 32 | // changes, clients read the merge request again. A merge publishes | 34 | // changes, clients read the merge request again. A merge publishes | ||
| 33 | // merge_request_merged, then pushed for the target branch. | 35 | // merge_request_merged, then pushed for the target branch. | ||
| @@ -171,7 +173,7 @@ message CommentAdded { | |||||
| 171 | // Gap tells the client that events were lost: the requested resume point is | 173 | // Gap tells the client that events were lost: the requested resume point is | ||
| 172 | // older than the server's buffer. It has no fields. The client should reload | 174 | // older than the server's buffer. It has no fields. The client should reload | ||
| 173 | // the state it displays (repositories, refs, merge requests and their | 175 | // the state it displays (repositories, refs, merge requests and their | ||
| 174 | - | // comments) and carry on with the next events. | 176 | + | // comments, CI runs and their jobs) and carry on with the next events. |
| 175 | message Gap {} | 177 | message Gap {} | ||
| 176 | 178 | ||||
| 177 | // RunUpdated reports a CI run that was created or whose status, conclusion or | 179 | // RunUpdated reports a CI run that was created or whose status, conclusion or | ||
M proto/ermite/run.proto +16 -0
| @@ -68,6 +68,11 @@ message Run { | |||
| 68 | 68 | google.protobuf.Timestamp started_at = 16; | |
| 69 | 69 | // CompletedAt is when this attempt completed. Unset until then. | |
| 70 | 70 | google.protobuf.Timestamp completed_at = 17; | |
| 71 | + | // Version counts the changes of this run on the server: it grows by at least | |
| 72 | + | // one at every change and never goes back, also across attempts. Events are | |
| 73 | + | // not delivered in order, and a client that holds two copies of a run keeps | |
| 74 | + | // the one with the higher version and drops the other. | |
| 75 | + | int64 version = 18; | |
| 71 | 76 | } | |
| 72 | 77 | ||
| 73 | 78 | // RunJob is a job of a run as people see it: the state of one job instance | |
| @@ -127,6 +132,11 @@ message RunJob { | |||
| 127 | 132 | google.protobuf.Timestamp started_at = 17; | |
| 128 | 133 | // CompletedAt is when the job completed. Unset until then. | |
| 129 | 134 | google.protobuf.Timestamp completed_at = 18; | |
| 135 | + | // Version counts the changes of this job on the server (status, conclusion, | |
| 136 | + | // runner, steps, with any attempt): it grows by at least one at every change | |
| 137 | + | // and never goes back. A client that holds two copies of a job keeps the | |
| 138 | + | // one with the higher version and drops the other, as for Run.version. | |
| 139 | + | int64 version = 19; | |
| 130 | 140 | } | |
| 131 | 141 | ||
| 132 | 142 | // Step is one step of a job, with where its lines are in the job log. | |
| @@ -405,6 +415,12 @@ message JobLogLine { | |||
| 405 | 415 | // Text is the line without its terminator, as in LogLine.text, including the | |
| 406 | 416 | // "##[group]" and "##[endgroup]" markers. Clients show it escaped. | |
| 407 | 417 | string text = 3; | |
| 418 | + | // Continued is true when the line continues the line before it, which was | |
| 419 | + | // too long for the runner to send whole (see LogLine.continued). Pieces keep | |
| 420 | + | // their own line numbers, so offsets are the same as the runner's; a client | |
| 421 | + | // that shows lines joins a continued line to the previous one. The raw log | |
| 422 | + | // file already has them joined. | |
| 423 | + | bool continued = 4; | |
| 408 | 424 | } | |
| 409 | 425 | ||
| 410 | 426 | // CancelRunRequest names the run to cancel. | |
| @@ -68,6 +68,11 @@ message Run { | |||||
| 68 | google.protobuf.Timestamp started_at = 16; | 68 | google.protobuf.Timestamp started_at = 16; | ||
| 69 | // CompletedAt is when this attempt completed. Unset until then. | 69 | // CompletedAt is when this attempt completed. Unset until then. | ||
| 70 | google.protobuf.Timestamp completed_at = 17; | 70 | google.protobuf.Timestamp completed_at = 17; | ||
| 71 | + | // Version counts the changes of this run on the server: it grows by at least | |||
| 72 | + | // one at every change and never goes back, also across attempts. Events are | |||
| 73 | + | // not delivered in order, and a client that holds two copies of a run keeps | |||
| 74 | + | // the one with the higher version and drops the other. | |||
| 75 | + | int64 version = 18; | |||
| 71 | } | 76 | } | ||
| 72 | 77 | ||||
| 73 | // RunJob is a job of a run as people see it: the state of one job instance | 78 | // RunJob is a job of a run as people see it: the state of one job instance | ||
| @@ -127,6 +132,11 @@ message RunJob { | |||||
| 127 | google.protobuf.Timestamp started_at = 17; | 132 | google.protobuf.Timestamp started_at = 17; | ||
| 128 | // CompletedAt is when the job completed. Unset until then. | 133 | // CompletedAt is when the job completed. Unset until then. | ||
| 129 | google.protobuf.Timestamp completed_at = 18; | 134 | google.protobuf.Timestamp completed_at = 18; | ||
| 135 | + | // Version counts the changes of this job on the server (status, conclusion, | |||
| 136 | + | // runner, steps, with any attempt): it grows by at least one at every change | |||
| 137 | + | // and never goes back. A client that holds two copies of a job keeps the | |||
| 138 | + | // one with the higher version and drops the other, as for Run.version. | |||
| 139 | + | int64 version = 19; | |||
| 130 | } | 140 | } | ||
| 131 | 141 | ||||
| 132 | // Step is one step of a job, with where its lines are in the job log. | 142 | // Step is one step of a job, with where its lines are in the job log. | ||
| @@ -405,6 +415,12 @@ message JobLogLine { | |||||
| 405 | // Text is the line without its terminator, as in LogLine.text, including the | 415 | // Text is the line without its terminator, as in LogLine.text, including the | ||
| 406 | // "##[group]" and "##[endgroup]" markers. Clients show it escaped. | 416 | // "##[group]" and "##[endgroup]" markers. Clients show it escaped. | ||
| 407 | string text = 3; | 417 | string text = 3; | ||
| 418 | + | // Continued is true when the line continues the line before it, which was | |||
| 419 | + | // too long for the runner to send whole (see LogLine.continued). Pieces keep | |||
| 420 | + | // their own line numbers, so offsets are the same as the runner's; a client | |||
| 421 | + | // that shows lines joins a continued line to the previous one. The raw log | |||
| 422 | + | // file already has them joined. | |||
| 423 | + | bool continued = 4; | |||
| 408 | } | 424 | } | ||
| 409 | 425 | ||||
| 410 | // CancelRunRequest names the run to cancel. | 426 | // CancelRunRequest names the run to cancel. | ||
M proto/ermite/runner.proto +273 -101
| @@ -4,6 +4,7 @@ syntax = "proto3"; | |||
| 4 | 4 | ||
| 5 | 5 | package ermite; | |
| 6 | 6 | ||
| 7 | + | import "google/protobuf/duration.proto"; | |
| 7 | 8 | import "google/protobuf/timestamp.proto"; | |
| 8 | 9 | ||
| 9 | 10 | // CiStatus is where a run, a job or a check is in its life. It is the same | |
| @@ -79,8 +80,7 @@ enum RunnerIsolation { | |||
| 79 | 80 | ||
| 80 | 81 | // CancelReason says why the server asks a runner to stop a job. | |
| 81 | 82 | enum CancelReason { | |
| 82 | - | // Not set. A WatchJobResponse with this reason is a keep-alive and carries | |
| 83 | - | // no request. | |
| 83 | + | // Not set. Never sent in a CancelRequest. | |
| 84 | 84 | CANCEL_REASON_UNSPECIFIED = 0; | |
| 85 | 85 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | |
| 86 | 86 | // ends CI_CONCLUSION_CANCELLED. | |
| @@ -88,38 +88,55 @@ enum CancelReason { | |||
| 88 | 88 | // Another job of the same matrix failed and the strategy has fail-fast. The | |
| 89 | 89 | // job ends CI_CONCLUSION_CANCELLED. | |
| 90 | 90 | CANCEL_REASON_FAIL_FAST = 2; | |
| 91 | - | // The job exceeded its timeout_minutes without the runner ending it. The | |
| 92 | - | // job ends CI_CONCLUSION_TIMED_OUT. | |
| 91 | + | // The job exceeded its time budget (see Job.timeout_minutes). The job ends | |
| 92 | + | // CI_CONCLUSION_TIMED_OUT. | |
| 93 | 93 | CANCEL_REASON_TIMEOUT = 3; | |
| 94 | 94 | } | |
| 95 | 95 | ||
| 96 | 96 | // RunnerService is what a CI runner (ermite-runner) calls to get jobs and | |
| 97 | 97 | // report on them. It is not for people: owners use RunService. | |
| 98 | 98 | // | |
| 99 | - | // Three credentials are used, each by different RPCs: | |
| 99 | + | // Three credentials are used, each by different RPCs. All are sent as | |
| 100 | + | // "Authorization: Bearer <secret>": | |
| 100 | 101 | // | |
| 101 | 102 | // - 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. | |
| 103 | + | // root token. | |
| 104 | + | // - FetchJob: the runner token returned by RegisterRunner. It identifies the | |
| 105 | + | // runner and only works for FetchJob. It is "ermiterun_" followed by 43 | |
| 106 | + | // base64url characters (32 random bytes, no padding). | |
| 107 | + | // - UpdateJob, AppendLog, Heartbeat, WatchJob, CompleteJob: the job token of | |
| 108 | + | // the Job that FetchJob returned. It is valid for that one job and that | |
| 109 | + | // one assignment. It is "ermitejob_" followed by 43 base64url characters | |
| 110 | + | // (32 random bytes, no padding). The job_id of the request must be the | |
| 111 | + | // one of the token. | |
| 110 | 112 | // | |
| 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. | |
| 113 | + | // Errors. unauthenticated means only that the credential is bad: missing, | |
| 114 | + | // unknown, expired, revoked, of the wrong kind for the RPC, or a job token | |
| 115 | + | // that was replaced (the job was given again, see FetchJob) or is older than | |
| 116 | + | // the 24 hours below. A runner that gets it for a job abandons the job at | |
| 117 | + | // once. A job token that is valid but belongs to another job than the job_id | |
| 118 | + | // of the request gets permission_denied. Repeated failed authentications are | |
| 119 | + | // rate limited per client address. | |
| 119 | 120 | // | |
| 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. | |
| 121 | + | // The end of a job. A job ends when its conclusion is set, by CompleteJob or | |
| 122 | + | // by the server (cancellation not honoured, lost runner, log limit, queue | |
| 123 | + | // timeout). From then on UpdateJob, AppendLog, Heartbeat and WatchJob answer | |
| 124 | + | // failed_precondition with a JobEnded error detail (a JobEnded message in the | |
| 125 | + | // Connect error details) that gives the conclusion; the runner stops the job | |
| 126 | + | // and sends nothing more. The job token is still recognised for 24 hours after | |
| 127 | + | // the end, so that these calls can say so and so that CompleteJob, the only | |
| 128 | + | // call that still does something, returns the stored result to a runner that | |
| 129 | + | // lost the answer of its last call or that was cut off by the server. After | |
| 130 | + | // the 24 hours every call gets unauthenticated. | |
| 131 | + | // | |
| 132 | + | // Secrets and trust. Runner tokens and job tokens are secrets: the server | |
| 133 | + | // never logs them and clients must not log them. The job token stays in the | |
| 134 | + | // runner host process; it is never given to the sandbox that runs the job's | |
| 135 | + | // code. A runner receives the jobs of every repository, with their workflow | |
| 136 | + | // files, contexts and (from CI-2) their code, and from CI-3 their secrets: the | |
| 137 | + | // runner:register scope therefore effectively gives read access to the code | |
| 138 | + | // and the secrets of every repository, private ones included. Give it only to | |
| 139 | + | // runners you operate. | |
| 123 | 140 | // | |
| 124 | 141 | // Servers that implement this service list "ci.runners" in | |
| 125 | 142 | // GetVersionResponse.features. | |
| @@ -130,37 +147,54 @@ service RunnerService { | |||
| 130 | 147 | // (a runner serves every repository), or the root token. Fails with | |
| 131 | 148 | // unauthenticated without a valid token, permission_denied without the | |
| 132 | 149 | // 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. | |
| 150 | + | // breaks a rule, and already_exists if name is taken by another runner and | |
| 151 | + | // replace is not set. | |
| 134 | 152 | rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse); | |
| 135 | 153 | // FetchJob asks for a job to run (long poll). The server holds the call | |
| 136 | 154 | // until a queued job matches the runner, or about 50 seconds (never more | |
| 137 | 155 | // 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. | |
| 156 | + | // runner calls again after retry_after. Clients set a call timeout above 50 | |
| 157 | + | // seconds (60 is suggested) and must not poll when they have no free | |
| 158 | + | // capacity. | |
| 140 | 159 | // | |
| 141 | 160 | // A job matches when every label of its runs-on is among the labels of the | |
| 142 | 161 | // runner (case-insensitive). A job is given to exactly one runner, even when | |
| 143 | 162 | // 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. | |
| 163 | + | // the response is sent. The job stays QUEUED for the owner until it is | |
| 164 | + | // acknowledged. | |
| 165 | + | // | |
| 166 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |
| 167 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |
| 168 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |
| 169 | + | // response was lost, or the runner died), the server cancels the assignment: | |
| 170 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |
| 171 | + | // and can be given to any runner, the same one included; nothing the old | |
| 172 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |
| 173 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |
| 174 | + | // and an error saying so. | |
| 175 | + | // | |
| 176 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |
| 177 | + | // a call with the same poll_id as an earlier call of the same runner that | |
| 178 | + | // assigned a job returns that same assignment (same Job, same token) as long | |
| 179 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |
| 180 | + | // | |
| 181 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |
| 182 | + | // not keep the job token, so the job fails with the lost-runner error after | |
| 183 | + | // the heartbeat timeout (see Heartbeat). | |
| 147 | 184 | // | |
| 148 | 185 | // Needs the runner token. Fails with unauthenticated when the runner was | |
| 149 | 186 | // removed (also ending a call in progress) and invalid_argument if labels | |
| 150 | - | // breaks a rule. | |
| 187 | + | // or poll_id break a rule. | |
| 151 | 188 | 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. | |
| 189 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the | |
| 190 | + | // job token. Each call is applied whole or not at all, and repeating an | |
| 191 | + | // identical call changes nothing, so a runner may retry after a network | |
| 192 | + | // error. To end the job use CompleteJob. | |
| 156 | 193 | // | |
| 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. | |
| 194 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition | |
| 195 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from | |
| 196 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail | |
| 197 | + | // if the job has ended. | |
| 164 | 198 | rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse); | |
| 165 | 199 | // AppendLog appends lines to the log of a job. Needs the job token. | |
| 166 | 200 | // | |
| @@ -172,46 +206,102 @@ service RunnerService { | |||
| 172 | 206 | // - offset equal to the server's line count: the lines are appended; | |
| 173 | 207 | // - offset below it: the first (count - offset) lines were already stored | |
| 174 | 208 | // (a retry after a lost response) and are skipped, and the rest is | |
| 175 | - | // appended; a request entirely stored changes nothing; | |
| 209 | + | // appended; a request entirely stored changes nothing. The skipped lines | |
| 210 | + | // are not compared with the stored ones: a retry whose lines differ from | |
| 211 | + | // the first attempt is silently dropped, so a client must resend the | |
| 212 | + | // same lines at the same offset; | |
| 176 | 213 | // - offset above it: out_of_range (a gap is a bug of the client; the | |
| 177 | 214 | // message gives the line count the server holds). | |
| 178 | 215 | // | |
| 179 | 216 | // 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. | |
| 217 | + | // itself. Offsets resume only after a network error within the same runner | |
| 218 | + | // process; they are not a way to resume a job after a crash. | |
| 219 | + | // | |
| 220 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |
| 221 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |
| 222 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |
| 223 | + | // | |
| 224 | + | // Steps. The step of a request must be at least the step of the last line | |
| 225 | + | // the server holds, because the log is ordered by step (applied lines only: | |
| 226 | + | // lines skipped as already stored are not checked); a lower step is | |
| 227 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |
| 228 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |
| 229 | + | // empty name until an UpdateJob gives one. | |
| 183 | 230 | // | |
| 184 | 231 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | |
| 185 | 232 | // the server (text, timestamps and separators). The call that would exceed | |
| 186 | 233 | // it appends nothing; the server adds a last line saying that the limit was | |
| 187 | 234 | // reached, ends the job with CI_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. | |
| 235 | + | // answers failed_precondition with a JobEnded detail like any call after the | |
| 236 | + | // end of a job. The runner then stops the job. | |
| 237 | + | // | |
| 238 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |
| 239 | + | // its AppendLog lines before it calls CompleteJob. | |
| 190 | 240 | // | |
| 191 | 241 | // Fails with invalid_argument if a rule is broken (step number, text with a | |
| 192 | - | // line feed or invalid UTF-8, missing time). | |
| 242 | + | // line feed or invalid UTF-8, missing time, a continued first line). | |
| 193 | 243 | rpc AppendLog(AppendLogRequest) returns (AppendLogResponse); | |
| 194 | 244 | // 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 CI_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. | |
| 245 | + | // the job. Needs the job token. The first one acknowledges the assignment | |
| 246 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as | |
| 247 | + | // the job runs. The deadline is 2 minutes after the later of the last | |
| 248 | + | // heartbeat and the start of the server (so a server restart does not fail | |
| 249 | + | // the jobs of runners that could not reach it): past it the server ends the | |
| 250 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |
| 251 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |
| 252 | + | // other calls do not keep a job alive. | |
| 253 | + | // | |
| 254 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |
| 255 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |
| 256 | + | // second tick, and keeps the job running meanwhile. | |
| 200 | 257 | rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); | |
| 201 | - | // WatchJob streams the requests to stop a job. Needs the job token. The | |
| 258 | + | // WatchJob streams what the runner must react to. Needs the job token. The | |
| 202 | 259 | // runner opens it when it starts the job and keeps it open; it reopens it | |
| 203 | - | // after any error other than unauthenticated. | |
| 260 | + | // after any error other than unauthenticated and the failed_precondition | |
| 261 | + | // that says the job ended, and after a clean close of | |
| 262 | + | // the stream that did not end with a JobEnded message. | |
| 204 | 263 | // | |
| 205 | 264 | // When the stream opens and a cancellation has already been requested, the | |
| 206 | 265 | // first message is that request, so a reconnect never misses one. Only the | |
| 207 | 266 | // 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 | |
| 267 | + | // second message when its time budget expires. The server also sends a | |
| 268 | + | // Keepalive at least every 30 seconds, which the client ignores. When the | |
| 269 | + | // job ends the server sends JobEnded, then closes the stream without error. | |
| 270 | + | // On a job that has already ended the call fails like the other RPCs | |
| 271 | + | // (failed_precondition with a JobEnded detail). | |
| 272 | + | // | |
| 273 | + | // On a CancelRequest the runner stops the job as the official runner does | |
| 212 | 274 | // (interrupt, then terminate, then kill) and reports the conclusion given | |
| 213 | - | // for the reason with UpdateJob. | |
| 275 | + | // for the reason with CompleteJob. | |
| 276 | + | // | |
| 277 | + | // The server does not trust the runner to obey. If no conclusion arrives | |
| 278 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |
| 279 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |
| 280 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |
| 281 | + | // saying that the runner did not answer, and the steps not completed are | |
| 282 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |
| 214 | 283 | rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse); | |
| 284 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |
| 285 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |
| 286 | + | // hours after the end of the job, however it ended. The call is applied whole | |
| 287 | + | // or not at all. Once the job has ended, the token works for no other RPC | |
| 288 | + | // (they answer failed_precondition with a JobEnded detail). | |
| 289 | + | // | |
| 290 | + | // It is idempotent. The first call that finds the job running ends it and | |
| 291 | + | // answers applied = true. Any later call, a retry after a lost response or a | |
| 292 | + | // call after the server ended the job itself, is not validated and changes | |
| 293 | + | // nothing: it answers OK with the stored conclusion and error and | |
| 294 | + | // applied = false (a runner whose conclusion differs from the stored one | |
| 295 | + | // simply logs it; the outputs it sent are dropped). | |
| 296 | + | // | |
| 297 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |
| 298 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |
| 299 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |
| 300 | + | // they never started and the job did not end by cancellation). | |
| 301 | + | // | |
| 302 | + | // Fails with invalid_argument if a rule is broken (including the output | |
| 303 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |
| 304 | + | rpc CompleteJob(CompleteJobRequest) returns (CompleteJobResponse); | |
| 215 | 305 | } | |
| 216 | 306 | ||
| 217 | 307 | // RegisterRunnerRequest describes the runner to register. | |
| @@ -237,6 +327,13 @@ message RegisterRunnerRequest { | |||
| 237 | 327 | // Os is the operating system: "linux". Other values are invalid_argument | |
| 238 | 328 | // (the set may grow with compatible additions). | |
| 239 | 329 | string os = 6; | |
| 330 | + | // Replace removes the runner that already has this name, if any, before | |
| 331 | + | // registering this one: its runner token is revoked at once, as in | |
| 332 | + | // RunService.RemoveRunner, and a job it is running is not affected. The new | |
| 333 | + | // runner gets a new runner_id. Needs the same scope as registering. Without | |
| 334 | + | // replace, a name in use is already_exists. It is how a runner re-registers | |
| 335 | + | // after losing its state. | |
| 336 | + | bool replace = 7; | |
| 240 | 337 | } | |
| 241 | 338 | ||
| 242 | 339 | // RegisterRunnerResponse holds the new runner and its secret. | |
| @@ -245,9 +342,10 @@ message RegisterRunnerResponse { | |||
| 245 | 342 | // a secret. | |
| 246 | 343 | string runner_id = 1; | |
| 247 | 344 | // 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. | |
| 345 | + | // Bearer <runner_token>" to FetchJob: "ermiterun_" and 43 base64url | |
| 346 | + | // characters (32 random bytes, no padding). It is returned once: the server | |
| 347 | + | // keeps only its SHA-256, the runner must store it (mode 0600). It stops | |
| 348 | + | // working when the runner is removed or replaced. | |
| 251 | 349 | string runner_token = 2; | |
| 252 | 350 | } | |
| 253 | 351 | ||
| @@ -258,6 +356,13 @@ message FetchJobRequest { | |||
| 258 | 356 | // a label of the runner (invalid_argument if not) and only jobs whose runs-on | |
| 259 | 357 | // labels are all among them are offered. | |
| 260 | 358 | repeated string labels = 1; | |
| 359 | + | // PollId identifies this poll, chosen by the client (for example 16 random | |
| 360 | + | // bytes in base64url), 1 to 64 characters among letters, digits, "_" and | |
| 361 | + | // "-". A client that retries a call whose response it did not get sends the | |
| 362 | + | // same poll_id, and gets the assignment the first call made, if it made one | |
| 363 | + | // (see FetchJob). A new poll uses a new id. Empty means no retry protection: | |
| 364 | + | // a lost assignment is then recovered by the acknowledgement timeout alone. | |
| 365 | + | string poll_id = 2; | |
| 261 | 366 | } | |
| 262 | 367 | ||
| 263 | 368 | // FetchJobResponse holds the job given to the runner, if any. | |
| @@ -265,6 +370,11 @@ message FetchJobResponse { | |||
| 265 | 370 | // Job is the job to run. Unset when no job was available before the end of | |
| 266 | 371 | // the poll. | |
| 267 | 372 | Job job = 1; | |
| 373 | + | // RetryAfter is how long the client waits before its next FetchJob. Unset or | |
| 374 | + | // zero means call again at once. The server sets it when it has no job for | |
| 375 | + | // the runner and wants to slow polling down (load, shutdown in progress); | |
| 376 | + | // clients obey it and may wait longer, never less. Unset when job is set. | |
| 377 | + | google.protobuf.Duration retry_after = 2; | |
| 268 | 378 | } | |
| 269 | 379 | ||
| 270 | 380 | // Job is a job given to a runner: everything needed to run it, nothing more. | |
| @@ -282,10 +392,13 @@ message Job { | |||
| 282 | 392 | // one more after each RunService.RerunRun. It is the "github.run_attempt". | |
| 283 | 393 | int32 attempt = 3; | |
| 284 | 394 | // 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. | |
| 395 | + | // AppendLog, Heartbeat, WatchJob and CompleteJob calls ("Authorization: | |
| 396 | + | // Bearer <job_token>"): "ermitejob_" and 43 base64url characters (32 random | |
| 397 | + | // bytes, no padding). It is random, valid from the assignment until the job | |
| 398 | + | // ends (and for CompleteJob 24 hours more), survives a server restart and | |
| 399 | + | // is shown only in this response, or in a repeat of the same poll_id. It | |
| 400 | + | // must stay in the runner host process and never reach the job's code. A new | |
| 401 | + | // assignment of the same job has a new token. | |
| 289 | 402 | string job_token = 4; | |
| 290 | 403 | // WorkflowPath is the path of the workflow file in the repository, for | |
| 291 | 404 | // example ".github/workflows/ci.yml". | |
| @@ -310,8 +423,11 @@ message Job { | |||
| 310 | 423 | repeated string labels = 10; | |
| 311 | 424 | // TimeoutMinutes is the timeout of the job in minutes: the value of the | |
| 312 | 425 | // 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. | |
| 426 | + | // runner enforces it from the moment it receives the job. The server counts | |
| 427 | + | // from the moment of the assignment and adds 60 seconds of slack, so the | |
| 428 | + | // runner's own timeout always comes first: when the server's deadline | |
| 429 | + | // passes it announces CANCEL_REASON_TIMEOUT, and ends the job itself after | |
| 430 | + | // 60 more seconds without a conclusion (see WatchJob). | |
| 315 | 431 | int32 timeout_minutes = 11; | |
| 316 | 432 | // Repo is the name of the repository. | |
| 317 | 433 | string repo = 12; | |
| @@ -323,7 +439,7 @@ message Job { | |||
| 323 | 439 | string ref = 14; | |
| 324 | 440 | } | |
| 325 | 441 | ||
| 326 | - | // UpdateJobRequest reports the steps and, in the last call, the end of a job. | |
| 442 | + | // UpdateJobRequest reports the steps of a running job. | |
| 327 | 443 | message UpdateJobRequest { | |
| 328 | 444 | // JobId is the Job.job_id. It must be the job of the token. | |
| 329 | 445 | string job_id = 1; | |
| @@ -333,28 +449,6 @@ message UpdateJobRequest { | |||
| 333 | 449 | // implicit "Set up job" is 1, the steps of the workflow follow in order, and | |
| 334 | 450 | // the implicit "Complete job" is last. | |
| 335 | 451 | 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 CI_CONCLUSION_SUCCESS, | |
| 346 | - | // CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or CI_CONCLUSION_TIMED_OUT; any | |
| 347 | - | // other value is invalid_argument. CI_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 CI_CONCLUSION_CANCELLED (or SKIPPED when they | |
| 350 | - | // never started and the job did not fail by cancellation). | |
| 351 | - | CiConclusion 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 CI_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 | 452 | } | |
| 359 | 453 | ||
| 360 | 454 | // StepUpdate is the state of one step as the runner knows it. | |
| @@ -383,13 +477,67 @@ message StepUpdate { | |||
| 383 | 477 | // UpdateJobResponse is empty. | |
| 384 | 478 | message UpdateJobResponse {} | |
| 385 | 479 | ||
| 480 | + | // CompleteJobRequest ends a job. | |
| 481 | + | message CompleteJobRequest { | |
| 482 | + | // JobId is the Job.job_id. It must be the job of the token. | |
| 483 | + | string job_id = 1; | |
| 484 | + | // Conclusion is how the job ended. Required: a runner reports | |
| 485 | + | // CI_CONCLUSION_SUCCESS, CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or | |
| 486 | + | // CI_CONCLUSION_TIMED_OUT; any other value is invalid_argument. Skipped is | |
| 487 | + | // decided by the server and never reported by a runner. | |
| 488 | + | CiConclusion conclusion = 2; | |
| 489 | + | // Outputs are the outputs of the job, evaluated by the runner. Names are 1 | |
| 490 | + | // to 255 characters among letters, digits, "_" and "-" and begin with a | |
| 491 | + | // letter or "_" and are case-insensitive: two names that differ only by case | |
| 492 | + | // are invalid_argument. Each value is at most 1 MiB, and all names and | |
| 493 | + | // values together at most 1 MiB (1,048,576 bytes); beyond that the whole call | |
| 494 | + | // is rejected with invalid_argument and the runner reports the failure of | |
| 495 | + | // the job without outputs, with a line in the log. | |
| 496 | + | map<string, string> outputs = 3; | |
| 497 | + | // RunnerShutdown is true when the runner ends the job because it was told to | |
| 498 | + | // stop (service stopped, signal). Only allowed with CI_CONCLUSION_CANCELLED. | |
| 499 | + | // The server then annotates the job with GitHub's message "The runner has | |
| 500 | + | // received a shutdown signal. This can happen when the runner service is | |
| 501 | + | // stopped, or a manually started runner is canceled." The conclusion | |
| 502 | + | // CANCELLED for this case is to be verified against a recording of GitHub. | |
| 503 | + | bool runner_shutdown = 4; | |
| 504 | + | // Steps are the final steps, with the same meaning as UpdateJobRequest.steps, | |
| 505 | + | // applied in the same atomic step as the conclusion. | |
| 506 | + | repeated StepUpdate steps = 5; | |
| 507 | + | } | |
| 508 | + | ||
| 509 | + | // CompleteJobResponse is the stored result of a job. | |
| 510 | + | message CompleteJobResponse { | |
| 511 | + | // Conclusion is the conclusion of the job as the server stored it. It is the | |
| 512 | + | // one of the request when applied is true. | |
| 513 | + | CiConclusion conclusion = 1; | |
| 514 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |
| 515 | + | // there is none. | |
| 516 | + | string error = 2; | |
| 517 | + | // Applied is true when this call ended the job, false when the job had | |
| 518 | + | // already ended (a retry, or the server ended it first) and nothing changed. | |
| 519 | + | bool applied = 3; | |
| 520 | + | } | |
| 521 | + | ||
| 522 | + | // JobEnded says that a job has ended. It is sent in WatchJob and attached, as | |
| 523 | + | // an error detail, to the failed_precondition errors that RunnerService returns | |
| 524 | + | // for a job that has ended. | |
| 525 | + | message JobEnded { | |
| 526 | + | // Conclusion is how the job ended. | |
| 527 | + | CiConclusion conclusion = 1; | |
| 528 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |
| 529 | + | // there is none. | |
| 530 | + | string error = 2; | |
| 531 | + | } | |
| 532 | + | ||
| 386 | 533 | // AppendLogRequest carries log lines of one step. | |
| 387 | 534 | message AppendLogRequest { | |
| 388 | 535 | // JobId is the Job.job_id. It must be the job of the token. | |
| 389 | 536 | string job_id = 1; | |
| 390 | 537 | // 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. | |
| 538 | + | // UpdateJobRequest.steps), at least 1, and at least the step of the last | |
| 539 | + | // stored line. A step that UpdateJob has not announced yet is created by the | |
| 540 | + | // first lines. See the rules on AppendLog. | |
| 393 | 541 | int32 step = 2; | |
| 394 | 542 | // Offset is the number of the first line of the request in the log of the | |
| 395 | 543 | // job, counted from 0 across all steps. See the rules on AppendLog. | |
| @@ -409,6 +557,15 @@ message LogLine { | |||
| 409 | 557 | // carriage return is kept. It is the line as GitHub's raw log has it, | |
| 410 | 558 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | |
| 411 | 559 | string text = 2; | |
| 560 | + | // Continued is true when this line is the continuation of the line before | |
| 561 | + | // it, which was too long to send whole. A runner cuts a line of more than | |
| 562 | + | // 64 KiB into pieces at UTF-8 character boundaries, each piece of at most | |
| 563 | + | // 64 KiB, and sets continued on every piece but the first. Each piece is one | |
| 564 | + | // line for numbering and offsets; the server and the raw log join the | |
| 565 | + | // pieces back, so the original line is rebuilt exactly. The first line of a | |
| 566 | + | // job cannot be continued (invalid_argument) and a continued line is in | |
| 567 | + | // the same step as the line before it (failed_precondition otherwise). | |
| 568 | + | bool continued = 3; | |
| 412 | 569 | } | |
| 413 | 570 | ||
| 414 | 571 | // AppendLogResponse tells how much of the log the server holds. | |
| @@ -433,13 +590,28 @@ message WatchJobRequest { | |||
| 433 | 590 | string job_id = 1; | |
| 434 | 591 | } | |
| 435 | 592 | ||
| 436 | - | // WatchJobResponse is one message of the stream: a request to cancel the job, | |
| 437 | - | // or a keep-alive. | |
| 593 | + | // WatchJobResponse is one message of the stream. | |
| 438 | 594 | 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 CI_CONCLUSION_CANCELLED. | |
| 595 | + | // Msg is what the message carries. New kinds may be added: a client ignores | |
| 596 | + | // the ones it does not know. | |
| 597 | + | oneof msg { | |
| 598 | + | // Keepalive: nothing to do, the stream is alive. | |
| 599 | + | Keepalive keepalive = 1; | |
| 600 | + | // Cancel: the job must stop. | |
| 601 | + | CancelRequest cancel = 2; | |
| 602 | + | // Ended: the job has ended. The last message of a stream: a close without | |
| 603 | + | // it means the client reconnects. | |
| 604 | + | JobEnded ended = 3; | |
| 605 | + | } | |
| 606 | + | } | |
| 607 | + | ||
| 608 | + | // Keepalive has no fields. | |
| 609 | + | message Keepalive {} | |
| 610 | + | ||
| 611 | + | // CancelRequest asks the runner to stop a job. | |
| 612 | + | message CancelRequest { | |
| 613 | + | // Reason is why the job must stop. New reasons may be added; a runner that | |
| 614 | + | // does not know one cancels the job and reports CI_CONCLUSION_CANCELLED. | |
| 443 | 615 | CancelReason reason = 1; | |
| 444 | 616 | // Message is a short human-readable explanation, for the job log, for | |
| 445 | 617 | // example "Canceled by the owner". At most 1,024 characters. | |
| @@ -4,6 +4,7 @@ syntax = "proto3"; | |||||
| 4 | 4 | ||||
| 5 | package ermite; | 5 | package ermite; | ||
| 6 | 6 | ||||
| 7 | + | import "google/protobuf/duration.proto"; | |||
| 7 | import "google/protobuf/timestamp.proto"; | 8 | import "google/protobuf/timestamp.proto"; | ||
| 8 | 9 | ||||
| 9 | // CiStatus is where a run, a job or a check is in its life. It is the same | 10 | // CiStatus is where a run, a job or a check is in its life. It is the same | ||
| @@ -79,8 +80,7 @@ enum RunnerIsolation { | |||||
| 79 | 80 | ||||
| 80 | // CancelReason says why the server asks a runner to stop a job. | 81 | // CancelReason says why the server asks a runner to stop a job. | ||
| 81 | enum CancelReason { | 82 | enum CancelReason { | ||
| 82 | - | // Not set. A WatchJobResponse with this reason is a keep-alive and carries | 83 | + | // Not set. Never sent in a CancelRequest. |
| 83 | - | // no request. | |||
| 84 | CANCEL_REASON_UNSPECIFIED = 0; | 84 | CANCEL_REASON_UNSPECIFIED = 0; | ||
| 85 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | 85 | // The owner cancelled the run (RunService.CancelRun, web or CLI). The job | ||
| 86 | // ends CI_CONCLUSION_CANCELLED. | 86 | // ends CI_CONCLUSION_CANCELLED. | ||
| @@ -88,38 +88,55 @@ enum CancelReason { | |||||
| 88 | // Another job of the same matrix failed and the strategy has fail-fast. The | 88 | // Another job of the same matrix failed and the strategy has fail-fast. The | ||
| 89 | // job ends CI_CONCLUSION_CANCELLED. | 89 | // job ends CI_CONCLUSION_CANCELLED. | ||
| 90 | CANCEL_REASON_FAIL_FAST = 2; | 90 | CANCEL_REASON_FAIL_FAST = 2; | ||
| 91 | - | // The job exceeded its timeout_minutes without the runner ending it. The | 91 | + | // The job exceeded its time budget (see Job.timeout_minutes). The job ends |
| 92 | - | // job ends CI_CONCLUSION_TIMED_OUT. | 92 | + | // CI_CONCLUSION_TIMED_OUT. |
| 93 | CANCEL_REASON_TIMEOUT = 3; | 93 | CANCEL_REASON_TIMEOUT = 3; | ||
| 94 | } | 94 | } | ||
| 95 | 95 | ||||
| 96 | // RunnerService is what a CI runner (ermite-runner) calls to get jobs and | 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. | 97 | // report on them. It is not for people: owners use RunService. | ||
| 98 | // | 98 | // | ||
| 99 | - | // Three credentials are used, each by different RPCs: | 99 | + | // Three credentials are used, each by different RPCs. All are sent as |
| 100 | + | // "Authorization: Bearer <secret>": | |||
| 100 | // | 101 | // | ||
| 101 | // - RegisterRunner: a stored token with the runner:register scope, or the | 102 | // - RegisterRunner: a stored token with the runner:register scope, or the | ||
| 102 | - | // root token, as "Authorization: Bearer <secret>". | 103 | + | // root token. |
| 103 | - | // - FetchJob: the runner token returned by RegisterRunner, as | 104 | + | // - FetchJob: the runner token returned by RegisterRunner. It identifies the |
| 104 | - | // "Authorization: Bearer <runner_token>". It identifies the runner. It | 105 | + | // runner and only works for FetchJob. It is "ermiterun_" followed by 43 |
| 105 | - | // only works for FetchJob. | 106 | + | // base64url characters (32 random bytes, no padding). |
| 106 | - | // - UpdateJob, AppendLog, Heartbeat, WatchJob: the job token of the Job that | 107 | + | // - UpdateJob, AppendLog, Heartbeat, WatchJob, CompleteJob: the job token of |
| 107 | - | // FetchJob returned, as "Authorization: Bearer <job_token>". It is valid | 108 | + | // the Job that FetchJob returned. It is valid for that one job and that |
| 108 | - | // for that one job and attempt, from its assignment until it ends. The | 109 | + | // one assignment. It is "ermitejob_" followed by 43 base64url characters |
| 109 | - | // job_id of the request must be the one of the token. | 110 | + | // (32 random bytes, no padding). The job_id of the request must be the |
| 111 | + | // one of the token. | |||
| 110 | // | 112 | // | ||
| 111 | - | // A request without a credential, with an unknown, expired or revoked one, or | 113 | + | // Errors. unauthenticated means only that the credential is bad: missing, |
| 112 | - | // with a credential of the wrong kind for the RPC, gets unauthenticated. A | 114 | + | // unknown, expired, revoked, of the wrong kind for the RPC, or a job token |
| 113 | - | // job token that is valid but belongs to another job than the job_id of the | 115 | + | // that was replaced (the job was given again, see FetchJob) or is older than |
| 114 | - | // request gets permission_denied. A job that has ended (concluded, cancelled, | 116 | + | // the 24 hours below. A runner that gets it for a job abandons the job at |
| 115 | - | // failed by the server, or whose runner was lost) no longer has a valid job | 117 | + | // once. A job token that is valid but belongs to another job than the job_id |
| 116 | - | // token: every later call gets unauthenticated, which a runner treats as "the | 118 | + | // of the request gets permission_denied. Repeated failed authentications are |
| 117 | - | // job is over" and stops sending anything for it. Repeated failed | 119 | + | // rate limited per client address. |
| 118 | - | // authentications are rate limited per client address. | |||
| 119 | // | 120 | // | ||
| 120 | - | // Runner tokens and job tokens are secrets: the server never logs them and | 121 | + | // The end of a job. A job ends when its conclusion is set, by CompleteJob or |
| 121 | - | // clients must not log them. The job token stays in the runner host process; it | 122 | + | // by the server (cancellation not honoured, lost runner, log limit, queue |
| 122 | - | // is never given to the sandbox that runs the job's code. | 123 | + | // timeout). From then on UpdateJob, AppendLog, Heartbeat and WatchJob answer |
| 124 | + | // failed_precondition with a JobEnded error detail (a JobEnded message in the | |||
| 125 | + | // Connect error details) that gives the conclusion; the runner stops the job | |||
| 126 | + | // and sends nothing more. The job token is still recognised for 24 hours after | |||
| 127 | + | // the end, so that these calls can say so and so that CompleteJob, the only | |||
| 128 | + | // call that still does something, returns the stored result to a runner that | |||
| 129 | + | // lost the answer of its last call or that was cut off by the server. After | |||
| 130 | + | // the 24 hours every call gets unauthenticated. | |||
| 131 | + | // | |||
| 132 | + | // Secrets and trust. Runner tokens and job tokens are secrets: the server | |||
| 133 | + | // never logs them and clients must not log them. The job token stays in the | |||
| 134 | + | // runner host process; it is never given to the sandbox that runs the job's | |||
| 135 | + | // code. A runner receives the jobs of every repository, with their workflow | |||
| 136 | + | // files, contexts and (from CI-2) their code, and from CI-3 their secrets: the | |||
| 137 | + | // runner:register scope therefore effectively gives read access to the code | |||
| 138 | + | // and the secrets of every repository, private ones included. Give it only to | |||
| 139 | + | // runners you operate. | |||
| 123 | // | 140 | // | ||
| 124 | // Servers that implement this service list "ci.runners" in | 141 | // Servers that implement this service list "ci.runners" in | ||
| 125 | // GetVersionResponse.features. | 142 | // GetVersionResponse.features. | ||
| @@ -130,37 +147,54 @@ service RunnerService { | |||||
| 130 | // (a runner serves every repository), or the root token. Fails with | 147 | // (a runner serves every repository), or the root token. Fails with | ||
| 131 | // unauthenticated without a valid token, permission_denied without the | 148 | // unauthenticated without a valid token, permission_denied without the | ||
| 132 | // scope or with a repository-restricted token, invalid_argument if a field | 149 | // 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. | 150 | + | // breaks a rule, and already_exists if name is taken by another runner and |
| 151 | + | // replace is not set. | |||
| 134 | rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse); | 152 | rpc RegisterRunner(RegisterRunnerRequest) returns (RegisterRunnerResponse); | ||
| 135 | // FetchJob asks for a job to run (long poll). The server holds the call | 153 | // 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 | 154 | // 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 | 155 | // 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 | 156 | + | // runner calls again after retry_after. Clients set a call timeout above 50 |
| 139 | - | // (60 is suggested) and must not poll when they have no free capacity. | 157 | + | // seconds (60 is suggested) and must not poll when they have no free |
| 158 | + | // capacity. | |||
| 140 | // | 159 | // | ||
| 141 | // A job matches when every label of its runs-on is among the labels of the | 160 | // 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 | 161 | // 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 | 162 | // 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 | 163 | + | // the response is sent. The job stays QUEUED for the owner until it is |
| 145 | - | // lost the job is not offered again: with no Heartbeat for 2 minutes the | 164 | + | // acknowledged. |
| 146 | - | // server fails it as a lost runner. | 165 | + | // |
| 166 | + | // Acknowledgement. The runner's first Heartbeat for the job is the | |||
| 167 | + | // acknowledgement, and the runner sends it as soon as it has the job, before | |||
| 168 | + | // anything else. If none arrives within 30 seconds of the assignment (the | |||
| 169 | + | // response was lost, or the runner died), the server cancels the assignment: | |||
| 170 | + | // the job token is revoked, the job goes back to QUEUED with a fresh token | |||
| 171 | + | // and can be given to any runner, the same one included; nothing the old | |||
| 172 | + | // assignment stored is kept. This is not a failure of the job. A job whose | |||
| 173 | + | // assignment is cancelled 3 times in a row is failed with CI_CONCLUSION_FAILURE | |||
| 174 | + | // and an error saying so. | |||
| 175 | + | // | |||
| 176 | + | // Retry after a lost response. poll_id lets a client retry a poll safely: | |||
| 177 | + | // a call with the same poll_id as an earlier call of the same runner that | |||
| 178 | + | // assigned a job returns that same assignment (same Job, same token) as long | |||
| 179 | + | // as it has not been acknowledged or cancelled, instead of taking another. | |||
| 180 | + | // | |||
| 181 | + | // A runner that crashed or whose host restarted cannot resume a job: it does | |||
| 182 | + | // not keep the job token, so the job fails with the lost-runner error after | |||
| 183 | + | // the heartbeat timeout (see Heartbeat). | |||
| 147 | // | 184 | // | ||
| 148 | // Needs the runner token. Fails with unauthenticated when the runner was | 185 | // Needs the runner token. Fails with unauthenticated when the runner was | ||
| 149 | // removed (also ending a call in progress) and invalid_argument if labels | 186 | // removed (also ending a call in progress) and invalid_argument if labels | ||
| 150 | - | // breaks a rule. | 187 | + | // or poll_id break a rule. |
| 151 | rpc FetchJob(FetchJobRequest) returns (FetchJobResponse); | 188 | rpc FetchJob(FetchJobRequest) returns (FetchJobResponse); | ||
| 152 | - | // UpdateJob reports the state of the steps of a job and, once, how the job | 189 | + | // UpdateJob reports the state of the steps of a job while it runs. Needs the |
| 153 | - | // ended. Needs the job token. Each call is applied whole or not at all, and | 190 | + | // job token. Each call is applied whole or not at all, and repeating an |
| 154 | - | // repeating an identical call changes nothing, so a runner may retry after a | 191 | + | // identical call changes nothing, so a runner may retry after a network |
| 155 | - | // network error. | 192 | + | // error. To end the job use CompleteJob. |
| 156 | // | 193 | // | ||
| 157 | - | // Fails with invalid_argument if a rule is broken (including the output | 194 | + | // Fails with invalid_argument if a rule is broken, with failed_precondition |
| 158 | - | // limits), and with failed_precondition if a step goes back (a COMPLETED | 195 | + | // if a step goes back (a COMPLETED step cannot change, a step cannot go from |
| 159 | - | // step cannot change, a step cannot go from IN_PROGRESS to PENDING). | 196 | + | // IN_PROGRESS to PENDING) and with failed_precondition and a JobEnded detail |
| 160 | - | // The call that carries a conclusion ends the job: its job token stops | 197 | + | // if the job has ended. |
| 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); | 198 | rpc UpdateJob(UpdateJobRequest) returns (UpdateJobResponse); | ||
| 165 | // AppendLog appends lines to the log of a job. Needs the job token. | 199 | // AppendLog appends lines to the log of a job. Needs the job token. | ||
| 166 | // | 200 | // | ||
| @@ -172,46 +206,102 @@ service RunnerService { | |||||
| 172 | // - offset equal to the server's line count: the lines are appended; | 206 | // - offset equal to the server's line count: the lines are appended; | ||
| 173 | // - offset below it: the first (count - offset) lines were already stored | 207 | // - 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 | 208 | // (a retry after a lost response) and are skipped, and the rest is | ||
| 175 | - | // appended; a request entirely stored changes nothing; | 209 | + | // appended; a request entirely stored changes nothing. The skipped lines |
| 210 | + | // are not compared with the stored ones: a retry whose lines differ from | |||
| 211 | + | // the first attempt is silently dropped, so a client must resend the | |||
| 212 | + | // same lines at the same offset; | |||
| 176 | // - offset above it: out_of_range (a gap is a bug of the client; the | 213 | // - offset above it: out_of_range (a gap is a bug of the client; the | ||
| 177 | // message gives the line count the server holds). | 214 | // message gives the line count the server holds). | ||
| 178 | // | 215 | // | ||
| 179 | // The response gives the line count after the call, so a client can check | 216 | // 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 | 217 | + | // itself. Offsets resume only after a network error within the same runner |
| 181 | - | // 64 KiB (65,536 bytes), all in the same step; more is invalid_argument. A | 218 | + | // process; they are not a way to resume a job after a crash. |
| 182 | - | // line of more than 64 KiB is cut by the client into several lines. | 219 | + | // |
| 220 | + | // A request has at most 1,000 lines whose text adds up to at most 64 KiB | |||
| 221 | + | // (65,536 bytes), all in the same step; more is invalid_argument. A line | |||
| 222 | + | // longer than that is cut by the client into pieces, see LogLine.continued. | |||
| 223 | + | // | |||
| 224 | + | // Steps. The step of a request must be at least the step of the last line | |||
| 225 | + | // the server holds, because the log is ordered by step (applied lines only: | |||
| 226 | + | // lines skipped as already stored are not checked); a lower step is | |||
| 227 | + | // failed_precondition. Lines for a step that UpdateJob has not announced | |||
| 228 | + | // create it: it starts IN_PROGRESS at the time of its first line, with an | |||
| 229 | + | // empty name until an UpdateJob gives one. | |||
| 183 | // | 230 | // | ||
| 184 | // The log of a job is limited to 512 MiB (536,870,912 bytes) as stored by | 231 | // 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 | 232 | // 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 | 233 | // it appends nothing; the server adds a last line saying that the limit was | ||
| 187 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | 234 | // reached, ends the job with CI_CONCLUSION_FAILURE and RunJob.error set, and | ||
| 188 | - | // answers resource_exhausted. The runner then stops the job and sends | 235 | + | // answers failed_precondition with a JobEnded detail like any call after the |
| 189 | - | // nothing more: its calls get unauthenticated. | 236 | + | // end of a job. The runner then stops the job. |
| 237 | + | // | |||
| 238 | + | // Lines sent after the end of the job are rejected, so a runner flushes all | |||
| 239 | + | // its AppendLog lines before it calls CompleteJob. | |||
| 190 | // | 240 | // | ||
| 191 | // Fails with invalid_argument if a rule is broken (step number, text with a | 241 | // Fails with invalid_argument if a rule is broken (step number, text with a | ||
| 192 | - | // line feed or invalid UTF-8, missing time). | 242 | + | // line feed or invalid UTF-8, missing time, a continued first line). |
| 193 | rpc AppendLog(AppendLogRequest) returns (AppendLogResponse); | 243 | rpc AppendLog(AppendLogRequest) returns (AppendLogResponse); | ||
| 194 | // Heartbeat tells the server that the runner is alive and still working on | 244 | // 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 | 245 | + | // the job. Needs the job token. The first one acknowledges the assignment |
| 196 | - | // long as the job runs, from assignment to the end. Without a heartbeat for | 246 | + | // (see FetchJob). The runner then calls it every 30 seconds for as long as |
| 197 | - | // 2 minutes the server ends the job with CI_CONCLUSION_FAILURE and the message | 247 | + | // the job runs. The deadline is 2 minutes after the later of the last |
| 198 | - | // "The self-hosted runner: <name> lost communication with the server...". | 248 | + | // heartbeat and the start of the server (so a server restart does not fail |
| 199 | - | // Only Heartbeat counts: other calls do not keep a job alive. | 249 | + | // the jobs of runners that could not reach it): past it the server ends the |
| 250 | + | // job with CI_CONCLUSION_FAILURE and the message "The self-hosted runner: | |||
| 251 | + | // <name> lost communication with the server...". Only Heartbeat counts: | |||
| 252 | + | // other calls do not keep a job alive. | |||
| 253 | + | // | |||
| 254 | + | // A runner that gets unavailable (or any network error) retries Heartbeat | |||
| 255 | + | // every 5 seconds until it succeeds, instead of waiting for its next 30 | |||
| 256 | + | // second tick, and keeps the job running meanwhile. | |||
| 200 | rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); | 257 | rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse); | ||
| 201 | - | // WatchJob streams the requests to stop a job. Needs the job token. The | 258 | + | // WatchJob streams what the runner must react to. Needs the job token. The |
| 202 | // runner opens it when it starts the job and keeps it open; it reopens it | 259 | // runner opens it when it starts the job and keeps it open; it reopens it | ||
| 203 | - | // after any error other than unauthenticated. | 260 | + | // after any error other than unauthenticated and the failed_precondition |
| 261 | + | // that says the job ended, and after a clean close of | |||
| 262 | + | // the stream that did not end with a JobEnded message. | |||
| 204 | // | 263 | // | ||
| 205 | // When the stream opens and a cancellation has already been requested, the | 264 | // 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 | 265 | // 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 | 266 | // 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 | 267 | + | // second message when its time budget expires. The server also sends a |
| 209 | - | // keep-alive message (reason CANCEL_REASON_UNSPECIFIED) at least every 30 | 268 | + | // Keepalive at least every 30 seconds, which the client ignores. When the |
| 210 | - | // seconds, which the client ignores. The stream ends without error when | 269 | + | // job ends the server sends JobEnded, then closes the stream without error. |
| 211 | - | // the job ends. The runner stops the job as the official runner does | 270 | + | // On a job that has already ended the call fails like the other RPCs |
| 271 | + | // (failed_precondition with a JobEnded detail). | |||
| 272 | + | // | |||
| 273 | + | // On a CancelRequest the runner stops the job as the official runner does | |||
| 212 | // (interrupt, then terminate, then kill) and reports the conclusion given | 274 | // (interrupt, then terminate, then kill) and reports the conclusion given | ||
| 213 | - | // for the reason with UpdateJob. | 275 | + | // for the reason with CompleteJob. |
| 276 | + | // | |||
| 277 | + | // The server does not trust the runner to obey. If no conclusion arrives | |||
| 278 | + | // within 60 seconds after a cancellation request, whatever the reason (user, | |||
| 279 | + | // fail-fast, timeout), the server ends the job itself: CI_CONCLUSION_CANCELLED | |||
| 280 | + | // for user and fail-fast, CI_CONCLUSION_TIMED_OUT for timeout, with an error | |||
| 281 | + | // saying that the runner did not answer, and the steps not completed are | |||
| 282 | + | // completed as cancelled. The runner's later CompleteJob returns that result. | |||
| 214 | rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse); | 283 | rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse); | ||
| 284 | + | // CompleteJob ends a job: it reports the final steps, the outputs and the | |||
| 285 | + | // conclusion. Needs the job token, which stays valid for this RPC for 24 | |||
| 286 | + | // hours after the end of the job, however it ended. The call is applied whole | |||
| 287 | + | // or not at all. Once the job has ended, the token works for no other RPC | |||
| 288 | + | // (they answer failed_precondition with a JobEnded detail). | |||
| 289 | + | // | |||
| 290 | + | // It is idempotent. The first call that finds the job running ends it and | |||
| 291 | + | // answers applied = true. Any later call, a retry after a lost response or a | |||
| 292 | + | // call after the server ended the job itself, is not validated and changes | |||
| 293 | + | // nothing: it answers OK with the stored conclusion and error and | |||
| 294 | + | // applied = false (a runner whose conclusion differs from the stored one | |||
| 295 | + | // simply logs it; the outputs it sent are dropped). | |||
| 296 | + | // | |||
| 297 | + | // Lines sent with AppendLog after this call are rejected: a runner flushes | |||
| 298 | + | // all its log lines first. Steps that were not reported COMPLETED when the job | |||
| 299 | + | // ends are completed by the server as CI_CONCLUSION_CANCELLED (or SKIPPED when | |||
| 300 | + | // they never started and the job did not end by cancellation). | |||
| 301 | + | // | |||
| 302 | + | // Fails with invalid_argument if a rule is broken (including the output | |||
| 303 | + | // limits) and failed_precondition if a step goes back, as in UpdateJob. | |||
| 304 | + | rpc CompleteJob(CompleteJobRequest) returns (CompleteJobResponse); | |||
| 215 | } | 305 | } | ||
| 216 | 306 | ||||
| 217 | // RegisterRunnerRequest describes the runner to register. | 307 | // RegisterRunnerRequest describes the runner to register. | ||
| @@ -237,6 +327,13 @@ message RegisterRunnerRequest { | |||||
| 237 | // Os is the operating system: "linux". Other values are invalid_argument | 327 | // Os is the operating system: "linux". Other values are invalid_argument | ||
| 238 | // (the set may grow with compatible additions). | 328 | // (the set may grow with compatible additions). | ||
| 239 | string os = 6; | 329 | string os = 6; | ||
| 330 | + | // Replace removes the runner that already has this name, if any, before | |||
| 331 | + | // registering this one: its runner token is revoked at once, as in | |||
| 332 | + | // RunService.RemoveRunner, and a job it is running is not affected. The new | |||
| 333 | + | // runner gets a new runner_id. Needs the same scope as registering. Without | |||
| 334 | + | // replace, a name in use is already_exists. It is how a runner re-registers | |||
| 335 | + | // after losing its state. | |||
| 336 | + | bool replace = 7; | |||
| 240 | } | 337 | } | ||
| 241 | 338 | ||||
| 242 | // RegisterRunnerResponse holds the new runner and its secret. | 339 | // RegisterRunnerResponse holds the new runner and its secret. | ||
| @@ -245,9 +342,10 @@ message RegisterRunnerResponse { | |||||
| 245 | // a secret. | 342 | // a secret. | ||
| 246 | string runner_id = 1; | 343 | string runner_id = 1; | ||
| 247 | // RunnerToken is the secret of the runner, to present as "Authorization: | 344 | // RunnerToken is the secret of the runner, to present as "Authorization: | ||
| 248 | - | // Bearer <runner_token>" to FetchJob. It starts with "ermiterun_" and is | 345 | + | // Bearer <runner_token>" to FetchJob: "ermiterun_" and 43 base64url |
| 249 | - | // returned once: the server keeps only its SHA-256, the runner must store | 346 | + | // characters (32 random bytes, no padding). It is returned once: the server |
| 250 | - | // it (mode 0600). It stops working when the runner is removed. | 347 | + | // keeps only its SHA-256, the runner must store it (mode 0600). It stops |
| 348 | + | // working when the runner is removed or replaced. | |||
| 251 | string runner_token = 2; | 349 | string runner_token = 2; | ||
| 252 | } | 350 | } | ||
| 253 | 351 | ||||
| @@ -258,6 +356,13 @@ message FetchJobRequest { | |||||
| 258 | // a label of the runner (invalid_argument if not) and only jobs whose runs-on | 356 | // a label of the runner (invalid_argument if not) and only jobs whose runs-on | ||
| 259 | // labels are all among them are offered. | 357 | // labels are all among them are offered. | ||
| 260 | repeated string labels = 1; | 358 | repeated string labels = 1; | ||
| 359 | + | // PollId identifies this poll, chosen by the client (for example 16 random | |||
| 360 | + | // bytes in base64url), 1 to 64 characters among letters, digits, "_" and | |||
| 361 | + | // "-". A client that retries a call whose response it did not get sends the | |||
| 362 | + | // same poll_id, and gets the assignment the first call made, if it made one | |||
| 363 | + | // (see FetchJob). A new poll uses a new id. Empty means no retry protection: | |||
| 364 | + | // a lost assignment is then recovered by the acknowledgement timeout alone. | |||
| 365 | + | string poll_id = 2; | |||
| 261 | } | 366 | } | ||
| 262 | 367 | ||||
| 263 | // FetchJobResponse holds the job given to the runner, if any. | 368 | // FetchJobResponse holds the job given to the runner, if any. | ||
| @@ -265,6 +370,11 @@ message FetchJobResponse { | |||||
| 265 | // Job is the job to run. Unset when no job was available before the end of | 370 | // Job is the job to run. Unset when no job was available before the end of | ||
| 266 | // the poll. | 371 | // the poll. | ||
| 267 | Job job = 1; | 372 | Job job = 1; | ||
| 373 | + | // RetryAfter is how long the client waits before its next FetchJob. Unset or | |||
| 374 | + | // zero means call again at once. The server sets it when it has no job for | |||
| 375 | + | // the runner and wants to slow polling down (load, shutdown in progress); | |||
| 376 | + | // clients obey it and may wait longer, never less. Unset when job is set. | |||
| 377 | + | google.protobuf.Duration retry_after = 2; | |||
| 268 | } | 378 | } | ||
| 269 | 379 | ||||
| 270 | // Job is a job given to a runner: everything needed to run it, nothing more. | 380 | // Job is a job given to a runner: everything needed to run it, nothing more. | ||
| @@ -282,10 +392,13 @@ message Job { | |||||
| 282 | // one more after each RunService.RerunRun. It is the "github.run_attempt". | 392 | // one more after each RunService.RerunRun. It is the "github.run_attempt". | ||
| 283 | int32 attempt = 3; | 393 | int32 attempt = 3; | ||
| 284 | // JobToken is the secret that authenticates this job's UpdateJob, | 394 | // JobToken is the secret that authenticates this job's UpdateJob, | ||
| 285 | - | // AppendLog, Heartbeat and WatchJob calls ("Authorization: Bearer | 395 | + | // AppendLog, Heartbeat, WatchJob and CompleteJob calls ("Authorization: |
| 286 | - | // <job_token>"). It is random, valid until the job ends, survives a server | 396 | + | // Bearer <job_token>"): "ermitejob_" and 43 base64url characters (32 random |
| 287 | - | // restart and is shown only in this response. It must stay in the runner | 397 | + | // bytes, no padding). It is random, valid from the assignment until the job |
| 288 | - | // host process and never reach the job's code. | 398 | + | // ends (and for CompleteJob 24 hours more), survives a server restart and |
| 399 | + | // is shown only in this response, or in a repeat of the same poll_id. It | |||
| 400 | + | // must stay in the runner host process and never reach the job's code. A new | |||
| 401 | + | // assignment of the same job has a new token. | |||
| 289 | string job_token = 4; | 402 | string job_token = 4; | ||
| 290 | // WorkflowPath is the path of the workflow file in the repository, for | 403 | // WorkflowPath is the path of the workflow file in the repository, for | ||
| 291 | // example ".github/workflows/ci.yml". | 404 | // example ".github/workflows/ci.yml". | ||
| @@ -310,8 +423,11 @@ message Job { | |||||
| 310 | repeated string labels = 10; | 423 | repeated string labels = 10; | ||
| 311 | // TimeoutMinutes is the timeout of the job in minutes: the value of the | 424 | // 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 | 425 | // 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 | 426 | + | // runner enforces it from the moment it receives the job. The server counts |
| 314 | - | // exceeds it by announcing CANCEL_REASON_TIMEOUT. | 427 | + | // from the moment of the assignment and adds 60 seconds of slack, so the |
| 428 | + | // runner's own timeout always comes first: when the server's deadline | |||
| 429 | + | // passes it announces CANCEL_REASON_TIMEOUT, and ends the job itself after | |||
| 430 | + | // 60 more seconds without a conclusion (see WatchJob). | |||
| 315 | int32 timeout_minutes = 11; | 431 | int32 timeout_minutes = 11; | ||
| 316 | // Repo is the name of the repository. | 432 | // Repo is the name of the repository. | ||
| 317 | string repo = 12; | 433 | string repo = 12; | ||
| @@ -323,7 +439,7 @@ message Job { | |||||
| 323 | string ref = 14; | 439 | string ref = 14; | ||
| 324 | } | 440 | } | ||
| 325 | 441 | ||||
| 326 | - | // UpdateJobRequest reports the steps and, in the last call, the end of a job. | 442 | + | // UpdateJobRequest reports the steps of a running job. |
| 327 | message UpdateJobRequest { | 443 | message UpdateJobRequest { | ||
| 328 | // JobId is the Job.job_id. It must be the job of the token. | 444 | // JobId is the Job.job_id. It must be the job of the token. | ||
| 329 | string job_id = 1; | 445 | string job_id = 1; | ||
| @@ -333,28 +449,6 @@ message UpdateJobRequest { | |||||
| 333 | // implicit "Set up job" is 1, the steps of the workflow follow in order, and | 449 | // implicit "Set up job" is 1, the steps of the workflow follow in order, and | ||
| 334 | // the implicit "Complete job" is last. | 450 | // the implicit "Complete job" is last. | ||
| 335 | repeated StepUpdate steps = 2; | 451 | 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 CI_CONCLUSION_SUCCESS, | |||
| 346 | - | // CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or CI_CONCLUSION_TIMED_OUT; any | |||
| 347 | - | // other value is invalid_argument. CI_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 CI_CONCLUSION_CANCELLED (or SKIPPED when they | |||
| 350 | - | // never started and the job did not fail by cancellation). | |||
| 351 | - | CiConclusion 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 CI_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 | } | 452 | } | ||
| 359 | 453 | ||||
| 360 | // StepUpdate is the state of one step as the runner knows it. | 454 | // StepUpdate is the state of one step as the runner knows it. | ||
| @@ -383,13 +477,67 @@ message StepUpdate { | |||||
| 383 | // UpdateJobResponse is empty. | 477 | // UpdateJobResponse is empty. | ||
| 384 | message UpdateJobResponse {} | 478 | message UpdateJobResponse {} | ||
| 385 | 479 | ||||
| 480 | + | // CompleteJobRequest ends a job. | |||
| 481 | + | message CompleteJobRequest { | |||
| 482 | + | // JobId is the Job.job_id. It must be the job of the token. | |||
| 483 | + | string job_id = 1; | |||
| 484 | + | // Conclusion is how the job ended. Required: a runner reports | |||
| 485 | + | // CI_CONCLUSION_SUCCESS, CI_CONCLUSION_FAILURE, CI_CONCLUSION_CANCELLED or | |||
| 486 | + | // CI_CONCLUSION_TIMED_OUT; any other value is invalid_argument. Skipped is | |||
| 487 | + | // decided by the server and never reported by a runner. | |||
| 488 | + | CiConclusion conclusion = 2; | |||
| 489 | + | // Outputs are the outputs of the job, evaluated by the runner. Names are 1 | |||
| 490 | + | // to 255 characters among letters, digits, "_" and "-" and begin with a | |||
| 491 | + | // letter or "_" and are case-insensitive: two names that differ only by case | |||
| 492 | + | // are invalid_argument. Each value is at most 1 MiB, and all names and | |||
| 493 | + | // values together at most 1 MiB (1,048,576 bytes); beyond that the whole call | |||
| 494 | + | // is rejected with invalid_argument and the runner reports the failure of | |||
| 495 | + | // the job without outputs, with a line in the log. | |||
| 496 | + | map<string, string> outputs = 3; | |||
| 497 | + | // RunnerShutdown is true when the runner ends the job because it was told to | |||
| 498 | + | // stop (service stopped, signal). Only allowed with CI_CONCLUSION_CANCELLED. | |||
| 499 | + | // The server then annotates the job with GitHub's message "The runner has | |||
| 500 | + | // received a shutdown signal. This can happen when the runner service is | |||
| 501 | + | // stopped, or a manually started runner is canceled." The conclusion | |||
| 502 | + | // CANCELLED for this case is to be verified against a recording of GitHub. | |||
| 503 | + | bool runner_shutdown = 4; | |||
| 504 | + | // Steps are the final steps, with the same meaning as UpdateJobRequest.steps, | |||
| 505 | + | // applied in the same atomic step as the conclusion. | |||
| 506 | + | repeated StepUpdate steps = 5; | |||
| 507 | + | } | |||
| 508 | + | ||||
| 509 | + | // CompleteJobResponse is the stored result of a job. | |||
| 510 | + | message CompleteJobResponse { | |||
| 511 | + | // Conclusion is the conclusion of the job as the server stored it. It is the | |||
| 512 | + | // one of the request when applied is true. | |||
| 513 | + | CiConclusion conclusion = 1; | |||
| 514 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |||
| 515 | + | // there is none. | |||
| 516 | + | string error = 2; | |||
| 517 | + | // Applied is true when this call ended the job, false when the job had | |||
| 518 | + | // already ended (a retry, or the server ended it first) and nothing changed. | |||
| 519 | + | bool applied = 3; | |||
| 520 | + | } | |||
| 521 | + | ||||
| 522 | + | // JobEnded says that a job has ended. It is sent in WatchJob and attached, as | |||
| 523 | + | // an error detail, to the failed_precondition errors that RunnerService returns | |||
| 524 | + | // for a job that has ended. | |||
| 525 | + | message JobEnded { | |||
| 526 | + | // Conclusion is how the job ended. | |||
| 527 | + | CiConclusion conclusion = 1; | |||
| 528 | + | // Error is the error text stored with the job, as RunJob.error. Empty when | |||
| 529 | + | // there is none. | |||
| 530 | + | string error = 2; | |||
| 531 | + | } | |||
| 532 | + | ||||
| 386 | // AppendLogRequest carries log lines of one step. | 533 | // AppendLogRequest carries log lines of one step. | ||
| 387 | message AppendLogRequest { | 534 | message AppendLogRequest { | ||
| 388 | // JobId is the Job.job_id. It must be the job of the token. | 535 | // JobId is the Job.job_id. It must be the job of the token. | ||
| 389 | string job_id = 1; | 536 | string job_id = 1; | ||
| 390 | // Step is the number of the step the lines belong to (see | 537 | // Step is the number of the step the lines belong to (see | ||
| 391 | - | // UpdateJobRequest.steps), at least 1. A step that UpdateJob has not | 538 | + | // UpdateJobRequest.steps), at least 1, and at least the step of the last |
| 392 | - | // announced yet is created by the first lines. | 539 | + | // stored line. A step that UpdateJob has not announced yet is created by the |
| 540 | + | // first lines. See the rules on AppendLog. | |||
| 393 | int32 step = 2; | 541 | int32 step = 2; | ||
| 394 | // Offset is the number of the first line of the request in the log of the | 542 | // 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. | 543 | // job, counted from 0 across all steps. See the rules on AppendLog. | ||
| @@ -409,6 +557,15 @@ message LogLine { | |||||
| 409 | // carriage return is kept. It is the line as GitHub's raw log has it, | 557 | // carriage return is kept. It is the line as GitHub's raw log has it, | ||
| 410 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | 558 | // including "##[group]" and "##[endgroup]" markers and "##[error]" lines. | ||
| 411 | string text = 2; | 559 | string text = 2; | ||
| 560 | + | // Continued is true when this line is the continuation of the line before | |||
| 561 | + | // it, which was too long to send whole. A runner cuts a line of more than | |||
| 562 | + | // 64 KiB into pieces at UTF-8 character boundaries, each piece of at most | |||
| 563 | + | // 64 KiB, and sets continued on every piece but the first. Each piece is one | |||
| 564 | + | // line for numbering and offsets; the server and the raw log join the | |||
| 565 | + | // pieces back, so the original line is rebuilt exactly. The first line of a | |||
| 566 | + | // job cannot be continued (invalid_argument) and a continued line is in | |||
| 567 | + | // the same step as the line before it (failed_precondition otherwise). | |||
| 568 | + | bool continued = 3; | |||
| 412 | } | 569 | } | ||
| 413 | 570 | ||||
| 414 | // AppendLogResponse tells how much of the log the server holds. | 571 | // AppendLogResponse tells how much of the log the server holds. | ||
| @@ -433,13 +590,28 @@ message WatchJobRequest { | |||||
| 433 | string job_id = 1; | 590 | string job_id = 1; | ||
| 434 | } | 591 | } | ||
| 435 | 592 | ||||
| 436 | - | // WatchJobResponse is one message of the stream: a request to cancel the job, | 593 | + | // WatchJobResponse is one message of the stream. |
| 437 | - | // or a keep-alive. | |||
| 438 | message WatchJobResponse { | 594 | message WatchJobResponse { | ||
| 439 | - | // Reason is why the job must stop. CANCEL_REASON_UNSPECIFIED marks a | 595 | + | // Msg is what the message carries. New kinds may be added: a client ignores |
| 440 | - | // keep-alive: the other fields are empty and the client ignores it. New | 596 | + | // the ones it does not know. |
| 441 | - | // reasons may be added; a runner that does not know one cancels the job and | 597 | + | oneof msg { |
| 442 | - | // reports CI_CONCLUSION_CANCELLED. | 598 | + | // Keepalive: nothing to do, the stream is alive. |
| 599 | + | Keepalive keepalive = 1; | |||
| 600 | + | // Cancel: the job must stop. | |||
| 601 | + | CancelRequest cancel = 2; | |||
| 602 | + | // Ended: the job has ended. The last message of a stream: a close without | |||
| 603 | + | // it means the client reconnects. | |||
| 604 | + | JobEnded ended = 3; | |||
| 605 | + | } | |||
| 606 | + | } | |||
| 607 | + | ||||
| 608 | + | // Keepalive has no fields. | |||
| 609 | + | message Keepalive {} | |||
| 610 | + | ||||
| 611 | + | // CancelRequest asks the runner to stop a job. | |||
| 612 | + | message CancelRequest { | |||
| 613 | + | // Reason is why the job must stop. New reasons may be added; a runner that | |||
| 614 | + | // does not know one cancels the job and reports CI_CONCLUSION_CANCELLED. | |||
| 443 | CancelReason reason = 1; | 615 | CancelReason reason = 1; | ||
| 444 | // Message is a short human-readable explanation, for the job log, for | 616 | // Message is a short human-readable explanation, for the job log, for | ||
| 445 | // example "Canceled by the owner". At most 1,024 characters. | 617 | // example "Canceled by the owner". At most 1,024 characters. | ||