detached 9d26b26

feat(proto): artifacts v4 for runners (#12)

commit
9d26b26e5e6f2c66781a446e07994ba46b35fa23
author
brice · · 2026-10-16 07:33 UTC
committer
bnema · · 2026-10-16 07:33 UTC
parent
89aa722
signature
SSH UnverifiedSHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivA The key is unknown
7 files · +2446 -4
M README.md +4 -2
@@ -2,7 +2,7 @@
22
33 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
44
5-Status: early development. The contract defines twelve services (below); the server that implements them is being built.
5+Status: early development. The contract defines fourteen services (below); the server that implements them is being built.
66
77 ```go
88 import "bnema.dev/ermite-api/gen/ermite"
@@ -27,13 +27,15 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
2727 | `SecretService` | `SetSecret`, `ListSecrets` (names only, never values), `DeleteSecret`; scopes instance, repository and environment |
2828 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`; same scopes |
2929 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
30+| `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`; artifacts v4 for CI runners, scoped to the run of the job token |
31+| `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact`; artifacts of a run for people |
3032 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
3133
3234 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
3335
3436 ## Layout
3537
36-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`, `ci_settings`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
38+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`, `ci_settings`, `artifact`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
3739 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
3840
3941 ## Generate
@@ -2,7 +2,7 @@
2 2
3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.3 The public API contract of [ermite](https://git.bnema.dev/bnema/ermite), a single-user Git forge: protobuf definitions for its ConnectRPC services and the Go code generated from them.
4 4
5-Status: early development. The contract defines twelve services (below); the server that implements them is being built.5+Status: early development. The contract defines fourteen services (below); the server that implements them is being built.
6 6
7 ```go7 ```go
8 import "bnema.dev/ermite-api/gen/ermite"8 import "bnema.dev/ermite-api/gen/ermite"
@@ -27,13 +27,15 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
27 | `SecretService` | `SetSecret`, `ListSecrets` (names only, never values), `DeleteSecret`; scopes instance, repository and environment |27 | `SecretService` | `SetSecret`, `ListSecrets` (names only, never values), `DeleteSecret`; scopes instance, repository and environment |
28 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`; same scopes |28 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`; same scopes |
29 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |29 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
30+| `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`; artifacts v4 for CI runners, scoped to the run of the job token |
31+| `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact`; artifacts of a run for people |
30 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |32 | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
31 33
32 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.34 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
33 35
34 ## Layout36 ## Layout
35 37
36-- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`, `ci_settings`) plus shared `types`. Checked by `buf lint` and `buf breaking`.38+- `proto/ermite/`: `.proto` files of the `ermite` package, one per service (`repo`, `git`, `merge_request`, `token`, `session`, `event`, `server`, `runner`, `run`, `ci_settings`, `artifact`) plus shared `types`. Checked by `buf lint` and `buf breaking`.
37 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.39 - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand.
38 40
39 ## Generate41 ## Generate
A gen/ermite/artifact.pb.go +1548 -0 generated file: collapsed
A gen/ermite/ermiteconnect/artifact.connect.go +530 -0
@@ -0,0 +1,530 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/artifact.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // JobArtifactServiceName is the fully-qualified name of the JobArtifactService service.
27+ JobArtifactServiceName = "ermite.JobArtifactService"
28+ // ArtifactServiceName is the fully-qualified name of the ArtifactService service.
29+ ArtifactServiceName = "ermite.ArtifactService"
30+)
31+
32+// These constants are the fully-qualified names of the RPCs defined in this package. They're
33+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
34+//
35+// Note that these are different from the fully-qualified method names used by
36+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
37+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
38+// period.
39+const (
40+ // JobArtifactServiceCreateJobArtifactProcedure is the fully-qualified name of the
41+ // JobArtifactService's CreateJobArtifact RPC.
42+ JobArtifactServiceCreateJobArtifactProcedure = "/ermite.JobArtifactService/CreateJobArtifact"
43+ // JobArtifactServiceUploadJobArtifactBlockProcedure is the fully-qualified name of the
44+ // JobArtifactService's UploadJobArtifactBlock RPC.
45+ JobArtifactServiceUploadJobArtifactBlockProcedure = "/ermite.JobArtifactService/UploadJobArtifactBlock"
46+ // JobArtifactServiceCommitJobArtifactProcedure is the fully-qualified name of the
47+ // JobArtifactService's CommitJobArtifact RPC.
48+ JobArtifactServiceCommitJobArtifactProcedure = "/ermite.JobArtifactService/CommitJobArtifact"
49+ // JobArtifactServiceFinalizeJobArtifactProcedure is the fully-qualified name of the
50+ // JobArtifactService's FinalizeJobArtifact RPC.
51+ JobArtifactServiceFinalizeJobArtifactProcedure = "/ermite.JobArtifactService/FinalizeJobArtifact"
52+ // JobArtifactServiceListJobArtifactsProcedure is the fully-qualified name of the
53+ // JobArtifactService's ListJobArtifacts RPC.
54+ JobArtifactServiceListJobArtifactsProcedure = "/ermite.JobArtifactService/ListJobArtifacts"
55+ // JobArtifactServiceDownloadJobArtifactProcedure is the fully-qualified name of the
56+ // JobArtifactService's DownloadJobArtifact RPC.
57+ JobArtifactServiceDownloadJobArtifactProcedure = "/ermite.JobArtifactService/DownloadJobArtifact"
58+ // JobArtifactServiceDeleteJobArtifactProcedure is the fully-qualified name of the
59+ // JobArtifactService's DeleteJobArtifact RPC.
60+ JobArtifactServiceDeleteJobArtifactProcedure = "/ermite.JobArtifactService/DeleteJobArtifact"
61+ // ArtifactServiceListArtifactsProcedure is the fully-qualified name of the ArtifactService's
62+ // ListArtifacts RPC.
63+ ArtifactServiceListArtifactsProcedure = "/ermite.ArtifactService/ListArtifacts"
64+ // ArtifactServiceDownloadArtifactProcedure is the fully-qualified name of the ArtifactService's
65+ // DownloadArtifact RPC.
66+ ArtifactServiceDownloadArtifactProcedure = "/ermite.ArtifactService/DownloadArtifact"
67+ // ArtifactServiceDeleteArtifactProcedure is the fully-qualified name of the ArtifactService's
68+ // DeleteArtifact RPC.
69+ ArtifactServiceDeleteArtifactProcedure = "/ermite.ArtifactService/DeleteArtifact"
70+)
71+
72+// JobArtifactServiceClient is a client for the ermite.JobArtifactService service.
73+type JobArtifactServiceClient interface {
74+ // CreateJobArtifact begins the upload of an artifact of the job's run and
75+ // returns its id. Fails with invalid_argument if the name or expires_at break
76+ // a rule, already_exists if an artifact of that name was finalized by a job
77+ // of the same attempt of the run (the uploader deletes it first to replace
78+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
79+ // repository already use all their space. A pending artifact of the same
80+ // job and name is discarded: a new create is a new upload.
81+ CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error)
82+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
83+ // client stream: the first message names the job, the artifact and the
84+ // block, every message may carry data, and the block is the data of all the
85+ // messages in order. The block is stored when the client closes the stream
86+ // and the server answers; a stream that breaks or fails stores nothing.
87+ // Storing a block id again replaces the block, so a retry is safe. Blocks
88+ // that the commit does not name are dropped.
89+ //
90+ // Fails with not_found if the artifact is not a pending artifact of this job,
91+ // failed_precondition if it is already committed, invalid_argument if a rule
92+ // of the messages is broken, and resource_exhausted if the blocks of the
93+ // artifact would exceed the size of one artifact or the space of the
94+ // repository.
95+ UploadJobArtifactBlock(context.Context) *connect.ClientStreamForClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse]
96+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
97+ // in that order (Azure's Put Block List). An empty list is an empty content
98+ // (a 0-byte upload). The server then knows the size and the digest, and
99+ // drops the blocks. A repeat of the same call after a success returns the
100+ // same result; a different list on a committed artifact is
101+ // failed_precondition. Fails with not_found if the artifact is not a pending
102+ // artifact of this job, and invalid_argument if a block id was never
103+ // uploaded or the list has more than 50,000 ids.
104+ CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error)
105+ // FinalizeJobArtifact makes the latest committed artifact of the job with
106+ // that name visible to the run, and returns it. An artifact of the same name
107+ // from an earlier attempt of the run is deleted at the same moment (a re-run
108+ // replaces what the first attempt uploaded). A repeat after a success
109+ // returns the same artifact. Fails with not_found if the job has no pending
110+ // artifact of that name, failed_precondition if it is not committed,
111+ // invalid_argument if digest is set and differs from the content, and
112+ // already_exists if a job of the same attempt finalized an artifact of that
113+ // name in the meantime.
114+ FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error)
115+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
116+ // all attempts, by id. An empty list is not an error.
117+ ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error)
118+ // DownloadJobArtifact streams the content of an artifact of the job's run.
119+ // The first message carries the artifact, then the chunks follow in order
120+ // from offset. A client that loses the stream calls again with the offset
121+ // after the last byte it received. Fails with not_found if the artifact is
122+ // not a finalized artifact of the run (or has expired), and out_of_range if
123+ // offset is beyond its size.
124+ DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadJobArtifactResponse], error)
125+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
126+ // run (upload-artifact's "overwrite", and its merge action's
127+ // "delete-merged"). Fails with not_found if there is none.
128+ DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error)
129+}
130+
131+// NewJobArtifactServiceClient constructs a client for the ermite.JobArtifactService service. By
132+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
133+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
134+// connect.WithGRPC() or connect.WithGRPCWeb() options.
135+//
136+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
137+// http://api.acme.com or https://acme.com/grpc).
138+func NewJobArtifactServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) JobArtifactServiceClient {
139+ baseURL = strings.TrimRight(baseURL, "/")
140+ jobArtifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("JobArtifactService").Methods()
141+ return &jobArtifactServiceClient{
142+ createJobArtifact: connect.NewClient[ermite.CreateJobArtifactRequest, ermite.CreateJobArtifactResponse](
143+ httpClient,
144+ baseURL+JobArtifactServiceCreateJobArtifactProcedure,
145+ connect.WithSchema(jobArtifactServiceMethods.ByName("CreateJobArtifact")),
146+ connect.WithClientOptions(opts...),
147+ ),
148+ uploadJobArtifactBlock: connect.NewClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse](
149+ httpClient,
150+ baseURL+JobArtifactServiceUploadJobArtifactBlockProcedure,
151+ connect.WithSchema(jobArtifactServiceMethods.ByName("UploadJobArtifactBlock")),
152+ connect.WithClientOptions(opts...),
153+ ),
154+ commitJobArtifact: connect.NewClient[ermite.CommitJobArtifactRequest, ermite.CommitJobArtifactResponse](
155+ httpClient,
156+ baseURL+JobArtifactServiceCommitJobArtifactProcedure,
157+ connect.WithSchema(jobArtifactServiceMethods.ByName("CommitJobArtifact")),
158+ connect.WithClientOptions(opts...),
159+ ),
160+ finalizeJobArtifact: connect.NewClient[ermite.FinalizeJobArtifactRequest, ermite.FinalizeJobArtifactResponse](
161+ httpClient,
162+ baseURL+JobArtifactServiceFinalizeJobArtifactProcedure,
163+ connect.WithSchema(jobArtifactServiceMethods.ByName("FinalizeJobArtifact")),
164+ connect.WithClientOptions(opts...),
165+ ),
166+ listJobArtifacts: connect.NewClient[ermite.ListJobArtifactsRequest, ermite.ListJobArtifactsResponse](
167+ httpClient,
168+ baseURL+JobArtifactServiceListJobArtifactsProcedure,
169+ connect.WithSchema(jobArtifactServiceMethods.ByName("ListJobArtifacts")),
170+ connect.WithClientOptions(opts...),
171+ ),
172+ downloadJobArtifact: connect.NewClient[ermite.DownloadJobArtifactRequest, ermite.DownloadJobArtifactResponse](
173+ httpClient,
174+ baseURL+JobArtifactServiceDownloadJobArtifactProcedure,
175+ connect.WithSchema(jobArtifactServiceMethods.ByName("DownloadJobArtifact")),
176+ connect.WithClientOptions(opts...),
177+ ),
178+ deleteJobArtifact: connect.NewClient[ermite.DeleteJobArtifactRequest, ermite.DeleteJobArtifactResponse](
179+ httpClient,
180+ baseURL+JobArtifactServiceDeleteJobArtifactProcedure,
181+ connect.WithSchema(jobArtifactServiceMethods.ByName("DeleteJobArtifact")),
182+ connect.WithClientOptions(opts...),
183+ ),
184+ }
185+}
186+
187+// jobArtifactServiceClient implements JobArtifactServiceClient.
188+type jobArtifactServiceClient struct {
189+ createJobArtifact *connect.Client[ermite.CreateJobArtifactRequest, ermite.CreateJobArtifactResponse]
190+ uploadJobArtifactBlock *connect.Client[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse]
191+ commitJobArtifact *connect.Client[ermite.CommitJobArtifactRequest, ermite.CommitJobArtifactResponse]
192+ finalizeJobArtifact *connect.Client[ermite.FinalizeJobArtifactRequest, ermite.FinalizeJobArtifactResponse]
193+ listJobArtifacts *connect.Client[ermite.ListJobArtifactsRequest, ermite.ListJobArtifactsResponse]
194+ downloadJobArtifact *connect.Client[ermite.DownloadJobArtifactRequest, ermite.DownloadJobArtifactResponse]
195+ deleteJobArtifact *connect.Client[ermite.DeleteJobArtifactRequest, ermite.DeleteJobArtifactResponse]
196+}
197+
198+// CreateJobArtifact calls ermite.JobArtifactService.CreateJobArtifact.
199+func (c *jobArtifactServiceClient) CreateJobArtifact(ctx context.Context, req *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error) {
200+ return c.createJobArtifact.CallUnary(ctx, req)
201+}
202+
203+// UploadJobArtifactBlock calls ermite.JobArtifactService.UploadJobArtifactBlock.
204+func (c *jobArtifactServiceClient) UploadJobArtifactBlock(ctx context.Context) *connect.ClientStreamForClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse] {
205+ return c.uploadJobArtifactBlock.CallClientStream(ctx)
206+}
207+
208+// CommitJobArtifact calls ermite.JobArtifactService.CommitJobArtifact.
209+func (c *jobArtifactServiceClient) CommitJobArtifact(ctx context.Context, req *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error) {
210+ return c.commitJobArtifact.CallUnary(ctx, req)
211+}
212+
213+// FinalizeJobArtifact calls ermite.JobArtifactService.FinalizeJobArtifact.
214+func (c *jobArtifactServiceClient) FinalizeJobArtifact(ctx context.Context, req *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error) {
215+ return c.finalizeJobArtifact.CallUnary(ctx, req)
216+}
217+
218+// ListJobArtifacts calls ermite.JobArtifactService.ListJobArtifacts.
219+func (c *jobArtifactServiceClient) ListJobArtifacts(ctx context.Context, req *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error) {
220+ return c.listJobArtifacts.CallUnary(ctx, req)
221+}
222+
223+// DownloadJobArtifact calls ermite.JobArtifactService.DownloadJobArtifact.
224+func (c *jobArtifactServiceClient) DownloadJobArtifact(ctx context.Context, req *connect.Request[ermite.DownloadJobArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadJobArtifactResponse], error) {
225+ return c.downloadJobArtifact.CallServerStream(ctx, req)
226+}
227+
228+// DeleteJobArtifact calls ermite.JobArtifactService.DeleteJobArtifact.
229+func (c *jobArtifactServiceClient) DeleteJobArtifact(ctx context.Context, req *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error) {
230+ return c.deleteJobArtifact.CallUnary(ctx, req)
231+}
232+
233+// JobArtifactServiceHandler is an implementation of the ermite.JobArtifactService service.
234+type JobArtifactServiceHandler interface {
235+ // CreateJobArtifact begins the upload of an artifact of the job's run and
236+ // returns its id. Fails with invalid_argument if the name or expires_at break
237+ // a rule, already_exists if an artifact of that name was finalized by a job
238+ // of the same attempt of the run (the uploader deletes it first to replace
239+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
240+ // repository already use all their space. A pending artifact of the same
241+ // job and name is discarded: a new create is a new upload.
242+ CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error)
243+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
244+ // client stream: the first message names the job, the artifact and the
245+ // block, every message may carry data, and the block is the data of all the
246+ // messages in order. The block is stored when the client closes the stream
247+ // and the server answers; a stream that breaks or fails stores nothing.
248+ // Storing a block id again replaces the block, so a retry is safe. Blocks
249+ // that the commit does not name are dropped.
250+ //
251+ // Fails with not_found if the artifact is not a pending artifact of this job,
252+ // failed_precondition if it is already committed, invalid_argument if a rule
253+ // of the messages is broken, and resource_exhausted if the blocks of the
254+ // artifact would exceed the size of one artifact or the space of the
255+ // repository.
256+ UploadJobArtifactBlock(context.Context, *connect.ClientStream[ermite.UploadJobArtifactBlockRequest]) (*connect.Response[ermite.UploadJobArtifactBlockResponse], error)
257+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
258+ // in that order (Azure's Put Block List). An empty list is an empty content
259+ // (a 0-byte upload). The server then knows the size and the digest, and
260+ // drops the blocks. A repeat of the same call after a success returns the
261+ // same result; a different list on a committed artifact is
262+ // failed_precondition. Fails with not_found if the artifact is not a pending
263+ // artifact of this job, and invalid_argument if a block id was never
264+ // uploaded or the list has more than 50,000 ids.
265+ CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error)
266+ // FinalizeJobArtifact makes the latest committed artifact of the job with
267+ // that name visible to the run, and returns it. An artifact of the same name
268+ // from an earlier attempt of the run is deleted at the same moment (a re-run
269+ // replaces what the first attempt uploaded). A repeat after a success
270+ // returns the same artifact. Fails with not_found if the job has no pending
271+ // artifact of that name, failed_precondition if it is not committed,
272+ // invalid_argument if digest is set and differs from the content, and
273+ // already_exists if a job of the same attempt finalized an artifact of that
274+ // name in the meantime.
275+ FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error)
276+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
277+ // all attempts, by id. An empty list is not an error.
278+ ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error)
279+ // DownloadJobArtifact streams the content of an artifact of the job's run.
280+ // The first message carries the artifact, then the chunks follow in order
281+ // from offset. A client that loses the stream calls again with the offset
282+ // after the last byte it received. Fails with not_found if the artifact is
283+ // not a finalized artifact of the run (or has expired), and out_of_range if
284+ // offset is beyond its size.
285+ DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest], *connect.ServerStream[ermite.DownloadJobArtifactResponse]) error
286+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
287+ // run (upload-artifact's "overwrite", and its merge action's
288+ // "delete-merged"). Fails with not_found if there is none.
289+ DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error)
290+}
291+
292+// NewJobArtifactServiceHandler builds an HTTP handler from the service implementation. It returns
293+// the path on which to mount the handler and the handler itself.
294+//
295+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
296+// and JSON codecs. They also support gzip compression.
297+func NewJobArtifactServiceHandler(svc JobArtifactServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
298+ jobArtifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("JobArtifactService").Methods()
299+ jobArtifactServiceCreateJobArtifactHandler := connect.NewUnaryHandler(
300+ JobArtifactServiceCreateJobArtifactProcedure,
301+ svc.CreateJobArtifact,
302+ connect.WithSchema(jobArtifactServiceMethods.ByName("CreateJobArtifact")),
303+ connect.WithHandlerOptions(opts...),
304+ )
305+ jobArtifactServiceUploadJobArtifactBlockHandler := connect.NewClientStreamHandler(
306+ JobArtifactServiceUploadJobArtifactBlockProcedure,
307+ svc.UploadJobArtifactBlock,
308+ connect.WithSchema(jobArtifactServiceMethods.ByName("UploadJobArtifactBlock")),
309+ connect.WithHandlerOptions(opts...),
310+ )
311+ jobArtifactServiceCommitJobArtifactHandler := connect.NewUnaryHandler(
312+ JobArtifactServiceCommitJobArtifactProcedure,
313+ svc.CommitJobArtifact,
314+ connect.WithSchema(jobArtifactServiceMethods.ByName("CommitJobArtifact")),
315+ connect.WithHandlerOptions(opts...),
316+ )
317+ jobArtifactServiceFinalizeJobArtifactHandler := connect.NewUnaryHandler(
318+ JobArtifactServiceFinalizeJobArtifactProcedure,
319+ svc.FinalizeJobArtifact,
320+ connect.WithSchema(jobArtifactServiceMethods.ByName("FinalizeJobArtifact")),
321+ connect.WithHandlerOptions(opts...),
322+ )
323+ jobArtifactServiceListJobArtifactsHandler := connect.NewUnaryHandler(
324+ JobArtifactServiceListJobArtifactsProcedure,
325+ svc.ListJobArtifacts,
326+ connect.WithSchema(jobArtifactServiceMethods.ByName("ListJobArtifacts")),
327+ connect.WithHandlerOptions(opts...),
328+ )
329+ jobArtifactServiceDownloadJobArtifactHandler := connect.NewServerStreamHandler(
330+ JobArtifactServiceDownloadJobArtifactProcedure,
331+ svc.DownloadJobArtifact,
332+ connect.WithSchema(jobArtifactServiceMethods.ByName("DownloadJobArtifact")),
333+ connect.WithHandlerOptions(opts...),
334+ )
335+ jobArtifactServiceDeleteJobArtifactHandler := connect.NewUnaryHandler(
336+ JobArtifactServiceDeleteJobArtifactProcedure,
337+ svc.DeleteJobArtifact,
338+ connect.WithSchema(jobArtifactServiceMethods.ByName("DeleteJobArtifact")),
339+ connect.WithHandlerOptions(opts...),
340+ )
341+ return "/ermite.JobArtifactService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342+ switch r.URL.Path {
343+ case JobArtifactServiceCreateJobArtifactProcedure:
344+ jobArtifactServiceCreateJobArtifactHandler.ServeHTTP(w, r)
345+ case JobArtifactServiceUploadJobArtifactBlockProcedure:
346+ jobArtifactServiceUploadJobArtifactBlockHandler.ServeHTTP(w, r)
347+ case JobArtifactServiceCommitJobArtifactProcedure:
348+ jobArtifactServiceCommitJobArtifactHandler.ServeHTTP(w, r)
349+ case JobArtifactServiceFinalizeJobArtifactProcedure:
350+ jobArtifactServiceFinalizeJobArtifactHandler.ServeHTTP(w, r)
351+ case JobArtifactServiceListJobArtifactsProcedure:
352+ jobArtifactServiceListJobArtifactsHandler.ServeHTTP(w, r)
353+ case JobArtifactServiceDownloadJobArtifactProcedure:
354+ jobArtifactServiceDownloadJobArtifactHandler.ServeHTTP(w, r)
355+ case JobArtifactServiceDeleteJobArtifactProcedure:
356+ jobArtifactServiceDeleteJobArtifactHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedJobArtifactServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedJobArtifactServiceHandler struct{}
365+
366+func (UnimplementedJobArtifactServiceHandler) CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.CreateJobArtifact is not implemented"))
368+}
369+
370+func (UnimplementedJobArtifactServiceHandler) UploadJobArtifactBlock(context.Context, *connect.ClientStream[ermite.UploadJobArtifactBlockRequest]) (*connect.Response[ermite.UploadJobArtifactBlockResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.UploadJobArtifactBlock is not implemented"))
372+}
373+
374+func (UnimplementedJobArtifactServiceHandler) CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.CommitJobArtifact is not implemented"))
376+}
377+
378+func (UnimplementedJobArtifactServiceHandler) FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.FinalizeJobArtifact is not implemented"))
380+}
381+
382+func (UnimplementedJobArtifactServiceHandler) ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.ListJobArtifacts is not implemented"))
384+}
385+
386+func (UnimplementedJobArtifactServiceHandler) DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest], *connect.ServerStream[ermite.DownloadJobArtifactResponse]) error {
387+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.DownloadJobArtifact is not implemented"))
388+}
389+
390+func (UnimplementedJobArtifactServiceHandler) DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.DeleteJobArtifact is not implemented"))
392+}
393+
394+// ArtifactServiceClient is a client for the ermite.ArtifactService service.
395+type ArtifactServiceClient interface {
396+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
397+ // artifacts of the repository use.
398+ ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error)
399+ // DownloadArtifact streams the content of an artifact, as
400+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
401+ // artifact is not an artifact of the repository, and out_of_range if offset
402+ // is beyond its size.
403+ DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadArtifactResponse], error)
404+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
405+ // artifact of the repository.
406+ DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error)
407+}
408+
409+// NewArtifactServiceClient constructs a client for the ermite.ArtifactService service. By default,
410+// it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and
411+// sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC()
412+// or connect.WithGRPCWeb() options.
413+//
414+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
415+// http://api.acme.com or https://acme.com/grpc).
416+func NewArtifactServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ArtifactServiceClient {
417+ baseURL = strings.TrimRight(baseURL, "/")
418+ artifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("ArtifactService").Methods()
419+ return &artifactServiceClient{
420+ listArtifacts: connect.NewClient[ermite.ListArtifactsRequest, ermite.ListArtifactsResponse](
421+ httpClient,
422+ baseURL+ArtifactServiceListArtifactsProcedure,
423+ connect.WithSchema(artifactServiceMethods.ByName("ListArtifacts")),
424+ connect.WithClientOptions(opts...),
425+ ),
426+ downloadArtifact: connect.NewClient[ermite.DownloadArtifactRequest, ermite.DownloadArtifactResponse](
427+ httpClient,
428+ baseURL+ArtifactServiceDownloadArtifactProcedure,
429+ connect.WithSchema(artifactServiceMethods.ByName("DownloadArtifact")),
430+ connect.WithClientOptions(opts...),
431+ ),
432+ deleteArtifact: connect.NewClient[ermite.DeleteArtifactRequest, ermite.DeleteArtifactResponse](
433+ httpClient,
434+ baseURL+ArtifactServiceDeleteArtifactProcedure,
435+ connect.WithSchema(artifactServiceMethods.ByName("DeleteArtifact")),
436+ connect.WithClientOptions(opts...),
437+ ),
438+ }
439+}
440+
441+// artifactServiceClient implements ArtifactServiceClient.
442+type artifactServiceClient struct {
443+ listArtifacts *connect.Client[ermite.ListArtifactsRequest, ermite.ListArtifactsResponse]
444+ downloadArtifact *connect.Client[ermite.DownloadArtifactRequest, ermite.DownloadArtifactResponse]
445+ deleteArtifact *connect.Client[ermite.DeleteArtifactRequest, ermite.DeleteArtifactResponse]
446+}
447+
448+// ListArtifacts calls ermite.ArtifactService.ListArtifacts.
449+func (c *artifactServiceClient) ListArtifacts(ctx context.Context, req *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error) {
450+ return c.listArtifacts.CallUnary(ctx, req)
451+}
452+
453+// DownloadArtifact calls ermite.ArtifactService.DownloadArtifact.
454+func (c *artifactServiceClient) DownloadArtifact(ctx context.Context, req *connect.Request[ermite.DownloadArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadArtifactResponse], error) {
455+ return c.downloadArtifact.CallServerStream(ctx, req)
456+}
457+
458+// DeleteArtifact calls ermite.ArtifactService.DeleteArtifact.
459+func (c *artifactServiceClient) DeleteArtifact(ctx context.Context, req *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error) {
460+ return c.deleteArtifact.CallUnary(ctx, req)
461+}
462+
463+// ArtifactServiceHandler is an implementation of the ermite.ArtifactService service.
464+type ArtifactServiceHandler interface {
465+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
466+ // artifacts of the repository use.
467+ ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error)
468+ // DownloadArtifact streams the content of an artifact, as
469+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
470+ // artifact is not an artifact of the repository, and out_of_range if offset
471+ // is beyond its size.
472+ DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest], *connect.ServerStream[ermite.DownloadArtifactResponse]) error
473+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
474+ // artifact of the repository.
475+ DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error)
476+}
477+
478+// NewArtifactServiceHandler builds an HTTP handler from the service implementation. It returns the
479+// path on which to mount the handler and the handler itself.
480+//
481+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
482+// and JSON codecs. They also support gzip compression.
483+func NewArtifactServiceHandler(svc ArtifactServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
484+ artifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("ArtifactService").Methods()
485+ artifactServiceListArtifactsHandler := connect.NewUnaryHandler(
486+ ArtifactServiceListArtifactsProcedure,
487+ svc.ListArtifacts,
488+ connect.WithSchema(artifactServiceMethods.ByName("ListArtifacts")),
489+ connect.WithHandlerOptions(opts...),
490+ )
491+ artifactServiceDownloadArtifactHandler := connect.NewServerStreamHandler(
492+ ArtifactServiceDownloadArtifactProcedure,
493+ svc.DownloadArtifact,
494+ connect.WithSchema(artifactServiceMethods.ByName("DownloadArtifact")),
495+ connect.WithHandlerOptions(opts...),
496+ )
497+ artifactServiceDeleteArtifactHandler := connect.NewUnaryHandler(
498+ ArtifactServiceDeleteArtifactProcedure,
499+ svc.DeleteArtifact,
500+ connect.WithSchema(artifactServiceMethods.ByName("DeleteArtifact")),
501+ connect.WithHandlerOptions(opts...),
502+ )
503+ return "/ermite.ArtifactService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
504+ switch r.URL.Path {
505+ case ArtifactServiceListArtifactsProcedure:
506+ artifactServiceListArtifactsHandler.ServeHTTP(w, r)
507+ case ArtifactServiceDownloadArtifactProcedure:
508+ artifactServiceDownloadArtifactHandler.ServeHTTP(w, r)
509+ case ArtifactServiceDeleteArtifactProcedure:
510+ artifactServiceDeleteArtifactHandler.ServeHTTP(w, r)
511+ default:
512+ http.NotFound(w, r)
513+ }
514+ })
515+}
516+
517+// UnimplementedArtifactServiceHandler returns CodeUnimplemented from all methods.
518+type UnimplementedArtifactServiceHandler struct{}
519+
520+func (UnimplementedArtifactServiceHandler) ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error) {
521+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.ListArtifacts is not implemented"))
522+}
523+
524+func (UnimplementedArtifactServiceHandler) DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest], *connect.ServerStream[ermite.DownloadArtifactResponse]) error {
525+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.DownloadArtifact is not implemented"))
526+}
527+
528+func (UnimplementedArtifactServiceHandler) DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error) {
529+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.DeleteArtifact is not implemented"))
530+}
@@ -0,0 +1,530 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/artifact.proto
6+
7+package ermiteconnect
8+
9+import (
10+ ermite "bnema.dev/ermite-api/gen/ermite"
11+ connect "connectrpc.com/connect"
12+ context "context"
13+ errors "errors"
14+ http "net/http"
15+ strings "strings"
16+)
17+
18+// This is a compile-time assertion to ensure that this generated file and the connect package are
19+// compatible. If you get a compiler error that this constant is not defined, this code was
20+// generated with a version of connect newer than the one compiled into your binary. You can fix the
21+// problem by either regenerating this code with an older version of connect or updating the connect
22+// version compiled into your binary.
23+const _ = connect.IsAtLeastVersion1_13_0
24+
25+const (
26+ // JobArtifactServiceName is the fully-qualified name of the JobArtifactService service.
27+ JobArtifactServiceName = "ermite.JobArtifactService"
28+ // ArtifactServiceName is the fully-qualified name of the ArtifactService service.
29+ ArtifactServiceName = "ermite.ArtifactService"
30+)
31+
32+// These constants are the fully-qualified names of the RPCs defined in this package. They're
33+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
34+//
35+// Note that these are different from the fully-qualified method names used by
36+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
37+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
38+// period.
39+const (
40+ // JobArtifactServiceCreateJobArtifactProcedure is the fully-qualified name of the
41+ // JobArtifactService's CreateJobArtifact RPC.
42+ JobArtifactServiceCreateJobArtifactProcedure = "/ermite.JobArtifactService/CreateJobArtifact"
43+ // JobArtifactServiceUploadJobArtifactBlockProcedure is the fully-qualified name of the
44+ // JobArtifactService's UploadJobArtifactBlock RPC.
45+ JobArtifactServiceUploadJobArtifactBlockProcedure = "/ermite.JobArtifactService/UploadJobArtifactBlock"
46+ // JobArtifactServiceCommitJobArtifactProcedure is the fully-qualified name of the
47+ // JobArtifactService's CommitJobArtifact RPC.
48+ JobArtifactServiceCommitJobArtifactProcedure = "/ermite.JobArtifactService/CommitJobArtifact"
49+ // JobArtifactServiceFinalizeJobArtifactProcedure is the fully-qualified name of the
50+ // JobArtifactService's FinalizeJobArtifact RPC.
51+ JobArtifactServiceFinalizeJobArtifactProcedure = "/ermite.JobArtifactService/FinalizeJobArtifact"
52+ // JobArtifactServiceListJobArtifactsProcedure is the fully-qualified name of the
53+ // JobArtifactService's ListJobArtifacts RPC.
54+ JobArtifactServiceListJobArtifactsProcedure = "/ermite.JobArtifactService/ListJobArtifacts"
55+ // JobArtifactServiceDownloadJobArtifactProcedure is the fully-qualified name of the
56+ // JobArtifactService's DownloadJobArtifact RPC.
57+ JobArtifactServiceDownloadJobArtifactProcedure = "/ermite.JobArtifactService/DownloadJobArtifact"
58+ // JobArtifactServiceDeleteJobArtifactProcedure is the fully-qualified name of the
59+ // JobArtifactService's DeleteJobArtifact RPC.
60+ JobArtifactServiceDeleteJobArtifactProcedure = "/ermite.JobArtifactService/DeleteJobArtifact"
61+ // ArtifactServiceListArtifactsProcedure is the fully-qualified name of the ArtifactService's
62+ // ListArtifacts RPC.
63+ ArtifactServiceListArtifactsProcedure = "/ermite.ArtifactService/ListArtifacts"
64+ // ArtifactServiceDownloadArtifactProcedure is the fully-qualified name of the ArtifactService's
65+ // DownloadArtifact RPC.
66+ ArtifactServiceDownloadArtifactProcedure = "/ermite.ArtifactService/DownloadArtifact"
67+ // ArtifactServiceDeleteArtifactProcedure is the fully-qualified name of the ArtifactService's
68+ // DeleteArtifact RPC.
69+ ArtifactServiceDeleteArtifactProcedure = "/ermite.ArtifactService/DeleteArtifact"
70+)
71+
72+// JobArtifactServiceClient is a client for the ermite.JobArtifactService service.
73+type JobArtifactServiceClient interface {
74+ // CreateJobArtifact begins the upload of an artifact of the job's run and
75+ // returns its id. Fails with invalid_argument if the name or expires_at break
76+ // a rule, already_exists if an artifact of that name was finalized by a job
77+ // of the same attempt of the run (the uploader deletes it first to replace
78+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
79+ // repository already use all their space. A pending artifact of the same
80+ // job and name is discarded: a new create is a new upload.
81+ CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error)
82+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
83+ // client stream: the first message names the job, the artifact and the
84+ // block, every message may carry data, and the block is the data of all the
85+ // messages in order. The block is stored when the client closes the stream
86+ // and the server answers; a stream that breaks or fails stores nothing.
87+ // Storing a block id again replaces the block, so a retry is safe. Blocks
88+ // that the commit does not name are dropped.
89+ //
90+ // Fails with not_found if the artifact is not a pending artifact of this job,
91+ // failed_precondition if it is already committed, invalid_argument if a rule
92+ // of the messages is broken, and resource_exhausted if the blocks of the
93+ // artifact would exceed the size of one artifact or the space of the
94+ // repository.
95+ UploadJobArtifactBlock(context.Context) *connect.ClientStreamForClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse]
96+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
97+ // in that order (Azure's Put Block List). An empty list is an empty content
98+ // (a 0-byte upload). The server then knows the size and the digest, and
99+ // drops the blocks. A repeat of the same call after a success returns the
100+ // same result; a different list on a committed artifact is
101+ // failed_precondition. Fails with not_found if the artifact is not a pending
102+ // artifact of this job, and invalid_argument if a block id was never
103+ // uploaded or the list has more than 50,000 ids.
104+ CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error)
105+ // FinalizeJobArtifact makes the latest committed artifact of the job with
106+ // that name visible to the run, and returns it. An artifact of the same name
107+ // from an earlier attempt of the run is deleted at the same moment (a re-run
108+ // replaces what the first attempt uploaded). A repeat after a success
109+ // returns the same artifact. Fails with not_found if the job has no pending
110+ // artifact of that name, failed_precondition if it is not committed,
111+ // invalid_argument if digest is set and differs from the content, and
112+ // already_exists if a job of the same attempt finalized an artifact of that
113+ // name in the meantime.
114+ FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error)
115+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
116+ // all attempts, by id. An empty list is not an error.
117+ ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error)
118+ // DownloadJobArtifact streams the content of an artifact of the job's run.
119+ // The first message carries the artifact, then the chunks follow in order
120+ // from offset. A client that loses the stream calls again with the offset
121+ // after the last byte it received. Fails with not_found if the artifact is
122+ // not a finalized artifact of the run (or has expired), and out_of_range if
123+ // offset is beyond its size.
124+ DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadJobArtifactResponse], error)
125+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
126+ // run (upload-artifact's "overwrite", and its merge action's
127+ // "delete-merged"). Fails with not_found if there is none.
128+ DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error)
129+}
130+
131+// NewJobArtifactServiceClient constructs a client for the ermite.JobArtifactService service. By
132+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
133+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
134+// connect.WithGRPC() or connect.WithGRPCWeb() options.
135+//
136+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
137+// http://api.acme.com or https://acme.com/grpc).
138+func NewJobArtifactServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) JobArtifactServiceClient {
139+ baseURL = strings.TrimRight(baseURL, "/")
140+ jobArtifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("JobArtifactService").Methods()
141+ return &jobArtifactServiceClient{
142+ createJobArtifact: connect.NewClient[ermite.CreateJobArtifactRequest, ermite.CreateJobArtifactResponse](
143+ httpClient,
144+ baseURL+JobArtifactServiceCreateJobArtifactProcedure,
145+ connect.WithSchema(jobArtifactServiceMethods.ByName("CreateJobArtifact")),
146+ connect.WithClientOptions(opts...),
147+ ),
148+ uploadJobArtifactBlock: connect.NewClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse](
149+ httpClient,
150+ baseURL+JobArtifactServiceUploadJobArtifactBlockProcedure,
151+ connect.WithSchema(jobArtifactServiceMethods.ByName("UploadJobArtifactBlock")),
152+ connect.WithClientOptions(opts...),
153+ ),
154+ commitJobArtifact: connect.NewClient[ermite.CommitJobArtifactRequest, ermite.CommitJobArtifactResponse](
155+ httpClient,
156+ baseURL+JobArtifactServiceCommitJobArtifactProcedure,
157+ connect.WithSchema(jobArtifactServiceMethods.ByName("CommitJobArtifact")),
158+ connect.WithClientOptions(opts...),
159+ ),
160+ finalizeJobArtifact: connect.NewClient[ermite.FinalizeJobArtifactRequest, ermite.FinalizeJobArtifactResponse](
161+ httpClient,
162+ baseURL+JobArtifactServiceFinalizeJobArtifactProcedure,
163+ connect.WithSchema(jobArtifactServiceMethods.ByName("FinalizeJobArtifact")),
164+ connect.WithClientOptions(opts...),
165+ ),
166+ listJobArtifacts: connect.NewClient[ermite.ListJobArtifactsRequest, ermite.ListJobArtifactsResponse](
167+ httpClient,
168+ baseURL+JobArtifactServiceListJobArtifactsProcedure,
169+ connect.WithSchema(jobArtifactServiceMethods.ByName("ListJobArtifacts")),
170+ connect.WithClientOptions(opts...),
171+ ),
172+ downloadJobArtifact: connect.NewClient[ermite.DownloadJobArtifactRequest, ermite.DownloadJobArtifactResponse](
173+ httpClient,
174+ baseURL+JobArtifactServiceDownloadJobArtifactProcedure,
175+ connect.WithSchema(jobArtifactServiceMethods.ByName("DownloadJobArtifact")),
176+ connect.WithClientOptions(opts...),
177+ ),
178+ deleteJobArtifact: connect.NewClient[ermite.DeleteJobArtifactRequest, ermite.DeleteJobArtifactResponse](
179+ httpClient,
180+ baseURL+JobArtifactServiceDeleteJobArtifactProcedure,
181+ connect.WithSchema(jobArtifactServiceMethods.ByName("DeleteJobArtifact")),
182+ connect.WithClientOptions(opts...),
183+ ),
184+ }
185+}
186+
187+// jobArtifactServiceClient implements JobArtifactServiceClient.
188+type jobArtifactServiceClient struct {
189+ createJobArtifact *connect.Client[ermite.CreateJobArtifactRequest, ermite.CreateJobArtifactResponse]
190+ uploadJobArtifactBlock *connect.Client[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse]
191+ commitJobArtifact *connect.Client[ermite.CommitJobArtifactRequest, ermite.CommitJobArtifactResponse]
192+ finalizeJobArtifact *connect.Client[ermite.FinalizeJobArtifactRequest, ermite.FinalizeJobArtifactResponse]
193+ listJobArtifacts *connect.Client[ermite.ListJobArtifactsRequest, ermite.ListJobArtifactsResponse]
194+ downloadJobArtifact *connect.Client[ermite.DownloadJobArtifactRequest, ermite.DownloadJobArtifactResponse]
195+ deleteJobArtifact *connect.Client[ermite.DeleteJobArtifactRequest, ermite.DeleteJobArtifactResponse]
196+}
197+
198+// CreateJobArtifact calls ermite.JobArtifactService.CreateJobArtifact.
199+func (c *jobArtifactServiceClient) CreateJobArtifact(ctx context.Context, req *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error) {
200+ return c.createJobArtifact.CallUnary(ctx, req)
201+}
202+
203+// UploadJobArtifactBlock calls ermite.JobArtifactService.UploadJobArtifactBlock.
204+func (c *jobArtifactServiceClient) UploadJobArtifactBlock(ctx context.Context) *connect.ClientStreamForClient[ermite.UploadJobArtifactBlockRequest, ermite.UploadJobArtifactBlockResponse] {
205+ return c.uploadJobArtifactBlock.CallClientStream(ctx)
206+}
207+
208+// CommitJobArtifact calls ermite.JobArtifactService.CommitJobArtifact.
209+func (c *jobArtifactServiceClient) CommitJobArtifact(ctx context.Context, req *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error) {
210+ return c.commitJobArtifact.CallUnary(ctx, req)
211+}
212+
213+// FinalizeJobArtifact calls ermite.JobArtifactService.FinalizeJobArtifact.
214+func (c *jobArtifactServiceClient) FinalizeJobArtifact(ctx context.Context, req *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error) {
215+ return c.finalizeJobArtifact.CallUnary(ctx, req)
216+}
217+
218+// ListJobArtifacts calls ermite.JobArtifactService.ListJobArtifacts.
219+func (c *jobArtifactServiceClient) ListJobArtifacts(ctx context.Context, req *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error) {
220+ return c.listJobArtifacts.CallUnary(ctx, req)
221+}
222+
223+// DownloadJobArtifact calls ermite.JobArtifactService.DownloadJobArtifact.
224+func (c *jobArtifactServiceClient) DownloadJobArtifact(ctx context.Context, req *connect.Request[ermite.DownloadJobArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadJobArtifactResponse], error) {
225+ return c.downloadJobArtifact.CallServerStream(ctx, req)
226+}
227+
228+// DeleteJobArtifact calls ermite.JobArtifactService.DeleteJobArtifact.
229+func (c *jobArtifactServiceClient) DeleteJobArtifact(ctx context.Context, req *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error) {
230+ return c.deleteJobArtifact.CallUnary(ctx, req)
231+}
232+
233+// JobArtifactServiceHandler is an implementation of the ermite.JobArtifactService service.
234+type JobArtifactServiceHandler interface {
235+ // CreateJobArtifact begins the upload of an artifact of the job's run and
236+ // returns its id. Fails with invalid_argument if the name or expires_at break
237+ // a rule, already_exists if an artifact of that name was finalized by a job
238+ // of the same attempt of the run (the uploader deletes it first to replace
239+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
240+ // repository already use all their space. A pending artifact of the same
241+ // job and name is discarded: a new create is a new upload.
242+ CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error)
243+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
244+ // client stream: the first message names the job, the artifact and the
245+ // block, every message may carry data, and the block is the data of all the
246+ // messages in order. The block is stored when the client closes the stream
247+ // and the server answers; a stream that breaks or fails stores nothing.
248+ // Storing a block id again replaces the block, so a retry is safe. Blocks
249+ // that the commit does not name are dropped.
250+ //
251+ // Fails with not_found if the artifact is not a pending artifact of this job,
252+ // failed_precondition if it is already committed, invalid_argument if a rule
253+ // of the messages is broken, and resource_exhausted if the blocks of the
254+ // artifact would exceed the size of one artifact or the space of the
255+ // repository.
256+ UploadJobArtifactBlock(context.Context, *connect.ClientStream[ermite.UploadJobArtifactBlockRequest]) (*connect.Response[ermite.UploadJobArtifactBlockResponse], error)
257+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
258+ // in that order (Azure's Put Block List). An empty list is an empty content
259+ // (a 0-byte upload). The server then knows the size and the digest, and
260+ // drops the blocks. A repeat of the same call after a success returns the
261+ // same result; a different list on a committed artifact is
262+ // failed_precondition. Fails with not_found if the artifact is not a pending
263+ // artifact of this job, and invalid_argument if a block id was never
264+ // uploaded or the list has more than 50,000 ids.
265+ CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error)
266+ // FinalizeJobArtifact makes the latest committed artifact of the job with
267+ // that name visible to the run, and returns it. An artifact of the same name
268+ // from an earlier attempt of the run is deleted at the same moment (a re-run
269+ // replaces what the first attempt uploaded). A repeat after a success
270+ // returns the same artifact. Fails with not_found if the job has no pending
271+ // artifact of that name, failed_precondition if it is not committed,
272+ // invalid_argument if digest is set and differs from the content, and
273+ // already_exists if a job of the same attempt finalized an artifact of that
274+ // name in the meantime.
275+ FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error)
276+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
277+ // all attempts, by id. An empty list is not an error.
278+ ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error)
279+ // DownloadJobArtifact streams the content of an artifact of the job's run.
280+ // The first message carries the artifact, then the chunks follow in order
281+ // from offset. A client that loses the stream calls again with the offset
282+ // after the last byte it received. Fails with not_found if the artifact is
283+ // not a finalized artifact of the run (or has expired), and out_of_range if
284+ // offset is beyond its size.
285+ DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest], *connect.ServerStream[ermite.DownloadJobArtifactResponse]) error
286+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
287+ // run (upload-artifact's "overwrite", and its merge action's
288+ // "delete-merged"). Fails with not_found if there is none.
289+ DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error)
290+}
291+
292+// NewJobArtifactServiceHandler builds an HTTP handler from the service implementation. It returns
293+// the path on which to mount the handler and the handler itself.
294+//
295+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
296+// and JSON codecs. They also support gzip compression.
297+func NewJobArtifactServiceHandler(svc JobArtifactServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
298+ jobArtifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("JobArtifactService").Methods()
299+ jobArtifactServiceCreateJobArtifactHandler := connect.NewUnaryHandler(
300+ JobArtifactServiceCreateJobArtifactProcedure,
301+ svc.CreateJobArtifact,
302+ connect.WithSchema(jobArtifactServiceMethods.ByName("CreateJobArtifact")),
303+ connect.WithHandlerOptions(opts...),
304+ )
305+ jobArtifactServiceUploadJobArtifactBlockHandler := connect.NewClientStreamHandler(
306+ JobArtifactServiceUploadJobArtifactBlockProcedure,
307+ svc.UploadJobArtifactBlock,
308+ connect.WithSchema(jobArtifactServiceMethods.ByName("UploadJobArtifactBlock")),
309+ connect.WithHandlerOptions(opts...),
310+ )
311+ jobArtifactServiceCommitJobArtifactHandler := connect.NewUnaryHandler(
312+ JobArtifactServiceCommitJobArtifactProcedure,
313+ svc.CommitJobArtifact,
314+ connect.WithSchema(jobArtifactServiceMethods.ByName("CommitJobArtifact")),
315+ connect.WithHandlerOptions(opts...),
316+ )
317+ jobArtifactServiceFinalizeJobArtifactHandler := connect.NewUnaryHandler(
318+ JobArtifactServiceFinalizeJobArtifactProcedure,
319+ svc.FinalizeJobArtifact,
320+ connect.WithSchema(jobArtifactServiceMethods.ByName("FinalizeJobArtifact")),
321+ connect.WithHandlerOptions(opts...),
322+ )
323+ jobArtifactServiceListJobArtifactsHandler := connect.NewUnaryHandler(
324+ JobArtifactServiceListJobArtifactsProcedure,
325+ svc.ListJobArtifacts,
326+ connect.WithSchema(jobArtifactServiceMethods.ByName("ListJobArtifacts")),
327+ connect.WithHandlerOptions(opts...),
328+ )
329+ jobArtifactServiceDownloadJobArtifactHandler := connect.NewServerStreamHandler(
330+ JobArtifactServiceDownloadJobArtifactProcedure,
331+ svc.DownloadJobArtifact,
332+ connect.WithSchema(jobArtifactServiceMethods.ByName("DownloadJobArtifact")),
333+ connect.WithHandlerOptions(opts...),
334+ )
335+ jobArtifactServiceDeleteJobArtifactHandler := connect.NewUnaryHandler(
336+ JobArtifactServiceDeleteJobArtifactProcedure,
337+ svc.DeleteJobArtifact,
338+ connect.WithSchema(jobArtifactServiceMethods.ByName("DeleteJobArtifact")),
339+ connect.WithHandlerOptions(opts...),
340+ )
341+ return "/ermite.JobArtifactService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342+ switch r.URL.Path {
343+ case JobArtifactServiceCreateJobArtifactProcedure:
344+ jobArtifactServiceCreateJobArtifactHandler.ServeHTTP(w, r)
345+ case JobArtifactServiceUploadJobArtifactBlockProcedure:
346+ jobArtifactServiceUploadJobArtifactBlockHandler.ServeHTTP(w, r)
347+ case JobArtifactServiceCommitJobArtifactProcedure:
348+ jobArtifactServiceCommitJobArtifactHandler.ServeHTTP(w, r)
349+ case JobArtifactServiceFinalizeJobArtifactProcedure:
350+ jobArtifactServiceFinalizeJobArtifactHandler.ServeHTTP(w, r)
351+ case JobArtifactServiceListJobArtifactsProcedure:
352+ jobArtifactServiceListJobArtifactsHandler.ServeHTTP(w, r)
353+ case JobArtifactServiceDownloadJobArtifactProcedure:
354+ jobArtifactServiceDownloadJobArtifactHandler.ServeHTTP(w, r)
355+ case JobArtifactServiceDeleteJobArtifactProcedure:
356+ jobArtifactServiceDeleteJobArtifactHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedJobArtifactServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedJobArtifactServiceHandler struct{}
365+
366+func (UnimplementedJobArtifactServiceHandler) CreateJobArtifact(context.Context, *connect.Request[ermite.CreateJobArtifactRequest]) (*connect.Response[ermite.CreateJobArtifactResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.CreateJobArtifact is not implemented"))
368+}
369+
370+func (UnimplementedJobArtifactServiceHandler) UploadJobArtifactBlock(context.Context, *connect.ClientStream[ermite.UploadJobArtifactBlockRequest]) (*connect.Response[ermite.UploadJobArtifactBlockResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.UploadJobArtifactBlock is not implemented"))
372+}
373+
374+func (UnimplementedJobArtifactServiceHandler) CommitJobArtifact(context.Context, *connect.Request[ermite.CommitJobArtifactRequest]) (*connect.Response[ermite.CommitJobArtifactResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.CommitJobArtifact is not implemented"))
376+}
377+
378+func (UnimplementedJobArtifactServiceHandler) FinalizeJobArtifact(context.Context, *connect.Request[ermite.FinalizeJobArtifactRequest]) (*connect.Response[ermite.FinalizeJobArtifactResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.FinalizeJobArtifact is not implemented"))
380+}
381+
382+func (UnimplementedJobArtifactServiceHandler) ListJobArtifacts(context.Context, *connect.Request[ermite.ListJobArtifactsRequest]) (*connect.Response[ermite.ListJobArtifactsResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.ListJobArtifacts is not implemented"))
384+}
385+
386+func (UnimplementedJobArtifactServiceHandler) DownloadJobArtifact(context.Context, *connect.Request[ermite.DownloadJobArtifactRequest], *connect.ServerStream[ermite.DownloadJobArtifactResponse]) error {
387+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.DownloadJobArtifact is not implemented"))
388+}
389+
390+func (UnimplementedJobArtifactServiceHandler) DeleteJobArtifact(context.Context, *connect.Request[ermite.DeleteJobArtifactRequest]) (*connect.Response[ermite.DeleteJobArtifactResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.JobArtifactService.DeleteJobArtifact is not implemented"))
392+}
393+
394+// ArtifactServiceClient is a client for the ermite.ArtifactService service.
395+type ArtifactServiceClient interface {
396+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
397+ // artifacts of the repository use.
398+ ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error)
399+ // DownloadArtifact streams the content of an artifact, as
400+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
401+ // artifact is not an artifact of the repository, and out_of_range if offset
402+ // is beyond its size.
403+ DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadArtifactResponse], error)
404+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
405+ // artifact of the repository.
406+ DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error)
407+}
408+
409+// NewArtifactServiceClient constructs a client for the ermite.ArtifactService service. By default,
410+// it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and
411+// sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC()
412+// or connect.WithGRPCWeb() options.
413+//
414+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
415+// http://api.acme.com or https://acme.com/grpc).
416+func NewArtifactServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ArtifactServiceClient {
417+ baseURL = strings.TrimRight(baseURL, "/")
418+ artifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("ArtifactService").Methods()
419+ return &artifactServiceClient{
420+ listArtifacts: connect.NewClient[ermite.ListArtifactsRequest, ermite.ListArtifactsResponse](
421+ httpClient,
422+ baseURL+ArtifactServiceListArtifactsProcedure,
423+ connect.WithSchema(artifactServiceMethods.ByName("ListArtifacts")),
424+ connect.WithClientOptions(opts...),
425+ ),
426+ downloadArtifact: connect.NewClient[ermite.DownloadArtifactRequest, ermite.DownloadArtifactResponse](
427+ httpClient,
428+ baseURL+ArtifactServiceDownloadArtifactProcedure,
429+ connect.WithSchema(artifactServiceMethods.ByName("DownloadArtifact")),
430+ connect.WithClientOptions(opts...),
431+ ),
432+ deleteArtifact: connect.NewClient[ermite.DeleteArtifactRequest, ermite.DeleteArtifactResponse](
433+ httpClient,
434+ baseURL+ArtifactServiceDeleteArtifactProcedure,
435+ connect.WithSchema(artifactServiceMethods.ByName("DeleteArtifact")),
436+ connect.WithClientOptions(opts...),
437+ ),
438+ }
439+}
440+
441+// artifactServiceClient implements ArtifactServiceClient.
442+type artifactServiceClient struct {
443+ listArtifacts *connect.Client[ermite.ListArtifactsRequest, ermite.ListArtifactsResponse]
444+ downloadArtifact *connect.Client[ermite.DownloadArtifactRequest, ermite.DownloadArtifactResponse]
445+ deleteArtifact *connect.Client[ermite.DeleteArtifactRequest, ermite.DeleteArtifactResponse]
446+}
447+
448+// ListArtifacts calls ermite.ArtifactService.ListArtifacts.
449+func (c *artifactServiceClient) ListArtifacts(ctx context.Context, req *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error) {
450+ return c.listArtifacts.CallUnary(ctx, req)
451+}
452+
453+// DownloadArtifact calls ermite.ArtifactService.DownloadArtifact.
454+func (c *artifactServiceClient) DownloadArtifact(ctx context.Context, req *connect.Request[ermite.DownloadArtifactRequest]) (*connect.ServerStreamForClient[ermite.DownloadArtifactResponse], error) {
455+ return c.downloadArtifact.CallServerStream(ctx, req)
456+}
457+
458+// DeleteArtifact calls ermite.ArtifactService.DeleteArtifact.
459+func (c *artifactServiceClient) DeleteArtifact(ctx context.Context, req *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error) {
460+ return c.deleteArtifact.CallUnary(ctx, req)
461+}
462+
463+// ArtifactServiceHandler is an implementation of the ermite.ArtifactService service.
464+type ArtifactServiceHandler interface {
465+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
466+ // artifacts of the repository use.
467+ ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error)
468+ // DownloadArtifact streams the content of an artifact, as
469+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
470+ // artifact is not an artifact of the repository, and out_of_range if offset
471+ // is beyond its size.
472+ DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest], *connect.ServerStream[ermite.DownloadArtifactResponse]) error
473+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
474+ // artifact of the repository.
475+ DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error)
476+}
477+
478+// NewArtifactServiceHandler builds an HTTP handler from the service implementation. It returns the
479+// path on which to mount the handler and the handler itself.
480+//
481+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
482+// and JSON codecs. They also support gzip compression.
483+func NewArtifactServiceHandler(svc ArtifactServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
484+ artifactServiceMethods := ermite.File_ermite_artifact_proto.Services().ByName("ArtifactService").Methods()
485+ artifactServiceListArtifactsHandler := connect.NewUnaryHandler(
486+ ArtifactServiceListArtifactsProcedure,
487+ svc.ListArtifacts,
488+ connect.WithSchema(artifactServiceMethods.ByName("ListArtifacts")),
489+ connect.WithHandlerOptions(opts...),
490+ )
491+ artifactServiceDownloadArtifactHandler := connect.NewServerStreamHandler(
492+ ArtifactServiceDownloadArtifactProcedure,
493+ svc.DownloadArtifact,
494+ connect.WithSchema(artifactServiceMethods.ByName("DownloadArtifact")),
495+ connect.WithHandlerOptions(opts...),
496+ )
497+ artifactServiceDeleteArtifactHandler := connect.NewUnaryHandler(
498+ ArtifactServiceDeleteArtifactProcedure,
499+ svc.DeleteArtifact,
500+ connect.WithSchema(artifactServiceMethods.ByName("DeleteArtifact")),
501+ connect.WithHandlerOptions(opts...),
502+ )
503+ return "/ermite.ArtifactService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
504+ switch r.URL.Path {
505+ case ArtifactServiceListArtifactsProcedure:
506+ artifactServiceListArtifactsHandler.ServeHTTP(w, r)
507+ case ArtifactServiceDownloadArtifactProcedure:
508+ artifactServiceDownloadArtifactHandler.ServeHTTP(w, r)
509+ case ArtifactServiceDeleteArtifactProcedure:
510+ artifactServiceDeleteArtifactHandler.ServeHTTP(w, r)
511+ default:
512+ http.NotFound(w, r)
513+ }
514+ })
515+}
516+
517+// UnimplementedArtifactServiceHandler returns CodeUnimplemented from all methods.
518+type UnimplementedArtifactServiceHandler struct{}
519+
520+func (UnimplementedArtifactServiceHandler) ListArtifacts(context.Context, *connect.Request[ermite.ListArtifactsRequest]) (*connect.Response[ermite.ListArtifactsResponse], error) {
521+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.ListArtifacts is not implemented"))
522+}
523+
524+func (UnimplementedArtifactServiceHandler) DownloadArtifact(context.Context, *connect.Request[ermite.DownloadArtifactRequest], *connect.ServerStream[ermite.DownloadArtifactResponse]) error {
525+ return connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.DownloadArtifact is not implemented"))
526+}
527+
528+func (UnimplementedArtifactServiceHandler) DeleteArtifact(context.Context, *connect.Request[ermite.DeleteArtifactRequest]) (*connect.Response[ermite.DeleteArtifactResponse], error) {
529+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ArtifactService.DeleteArtifact is not implemented"))
530+}
M gen/ermite/server.pb.go +2 -1 generated file: collapsed
M gen_test.go +10 -0
@@ -78,6 +78,16 @@ func TestProcedures(t *testing.T) {
7878 {ermiteconnect.EnvironmentServiceCreateEnvironmentProcedure, "/ermite.EnvironmentService/CreateEnvironment"},
7979 {ermiteconnect.EnvironmentServiceListEnvironmentsProcedure, "/ermite.EnvironmentService/ListEnvironments"},
8080 {ermiteconnect.EnvironmentServiceDeleteEnvironmentProcedure, "/ermite.EnvironmentService/DeleteEnvironment"},
81+ {ermiteconnect.JobArtifactServiceCreateJobArtifactProcedure, "/ermite.JobArtifactService/CreateJobArtifact"},
82+ {ermiteconnect.JobArtifactServiceUploadJobArtifactBlockProcedure, "/ermite.JobArtifactService/UploadJobArtifactBlock"},
83+ {ermiteconnect.JobArtifactServiceCommitJobArtifactProcedure, "/ermite.JobArtifactService/CommitJobArtifact"},
84+ {ermiteconnect.JobArtifactServiceFinalizeJobArtifactProcedure, "/ermite.JobArtifactService/FinalizeJobArtifact"},
85+ {ermiteconnect.JobArtifactServiceListJobArtifactsProcedure, "/ermite.JobArtifactService/ListJobArtifacts"},
86+ {ermiteconnect.JobArtifactServiceDownloadJobArtifactProcedure, "/ermite.JobArtifactService/DownloadJobArtifact"},
87+ {ermiteconnect.JobArtifactServiceDeleteJobArtifactProcedure, "/ermite.JobArtifactService/DeleteJobArtifact"},
88+ {ermiteconnect.ArtifactServiceListArtifactsProcedure, "/ermite.ArtifactService/ListArtifacts"},
89+ {ermiteconnect.ArtifactServiceDownloadArtifactProcedure, "/ermite.ArtifactService/DownloadArtifact"},
90+ {ermiteconnect.ArtifactServiceDeleteArtifactProcedure, "/ermite.ArtifactService/DeleteArtifact"},
8191 }
8292 for _, tt := range tests {
8393 if tt.got != tt.want {
@@ -78,6 +78,16 @@ func TestProcedures(t *testing.T) {
78 {ermiteconnect.EnvironmentServiceCreateEnvironmentProcedure, "/ermite.EnvironmentService/CreateEnvironment"},78 {ermiteconnect.EnvironmentServiceCreateEnvironmentProcedure, "/ermite.EnvironmentService/CreateEnvironment"},
79 {ermiteconnect.EnvironmentServiceListEnvironmentsProcedure, "/ermite.EnvironmentService/ListEnvironments"},79 {ermiteconnect.EnvironmentServiceListEnvironmentsProcedure, "/ermite.EnvironmentService/ListEnvironments"},
80 {ermiteconnect.EnvironmentServiceDeleteEnvironmentProcedure, "/ermite.EnvironmentService/DeleteEnvironment"},80 {ermiteconnect.EnvironmentServiceDeleteEnvironmentProcedure, "/ermite.EnvironmentService/DeleteEnvironment"},
81+ {ermiteconnect.JobArtifactServiceCreateJobArtifactProcedure, "/ermite.JobArtifactService/CreateJobArtifact"},
82+ {ermiteconnect.JobArtifactServiceUploadJobArtifactBlockProcedure, "/ermite.JobArtifactService/UploadJobArtifactBlock"},
83+ {ermiteconnect.JobArtifactServiceCommitJobArtifactProcedure, "/ermite.JobArtifactService/CommitJobArtifact"},
84+ {ermiteconnect.JobArtifactServiceFinalizeJobArtifactProcedure, "/ermite.JobArtifactService/FinalizeJobArtifact"},
85+ {ermiteconnect.JobArtifactServiceListJobArtifactsProcedure, "/ermite.JobArtifactService/ListJobArtifacts"},
86+ {ermiteconnect.JobArtifactServiceDownloadJobArtifactProcedure, "/ermite.JobArtifactService/DownloadJobArtifact"},
87+ {ermiteconnect.JobArtifactServiceDeleteJobArtifactProcedure, "/ermite.JobArtifactService/DeleteJobArtifact"},
88+ {ermiteconnect.ArtifactServiceListArtifactsProcedure, "/ermite.ArtifactService/ListArtifacts"},
89+ {ermiteconnect.ArtifactServiceDownloadArtifactProcedure, "/ermite.ArtifactService/DownloadArtifact"},
90+ {ermiteconnect.ArtifactServiceDeleteArtifactProcedure, "/ermite.ArtifactService/DeleteArtifact"},
81 }91 }
82 for _, tt := range tests {92 for _, tt := range tests {
83 if tt.got != tt.want {93 if tt.got != tt.want {
A proto/ermite/artifact.proto +350 -0
@@ -0,0 +1,350 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// Artifact is a file that a job of a run uploaded with actions/upload-artifact
10+// (artifacts v4): in most cases a zip archive, or a single file uploaded as is
11+// ("archive: false"). It belongs to a run, not to one attempt: every job of the
12+// run, in any attempt, sees the artifacts of the run. Only finalized artifacts
13+// are described; an upload in progress is invisible.
14+message Artifact {
15+ // Id identifies the artifact on the server: unique across repositories and
16+ // runs, never reused, and higher for a later artifact. It is GitHub's
17+ // "artifact id" (databaseId), the id of the REST API.
18+ int64 id = 1;
19+ // Repo is the name of the repository of the run.
20+ string repo = 2;
21+ // RunId is the run the artifact belongs to.
22+ int64 run_id = 3;
23+ // Attempt is the attempt of the run whose job uploaded it.
24+ int32 attempt = 4;
25+ // JobId is the Job.job_id of the job that uploaded it.
26+ string job_id = 5;
27+ // Name is the name of the artifact, unique among the artifacts of the run:
28+ // 1 to 256 characters of valid UTF-8, without control characters, '"', ':',
29+ // '<', '>', '|', '*', '?', '\' or '/' (the characters @actions/artifact
30+ // refuses).
31+ string name = 6;
32+ // Size is the number of bytes of the content.
33+ int64 size = 7;
34+ // Digest is the SHA-256 of the content, "sha256:" and 64 lowercase
35+ // hexadecimal characters, as GitHub gives it.
36+ string digest = 8;
37+ // MediaType is the media type of the content: "application/zip" for an
38+ // archive, the type the uploader gave for a file uploaded as is.
39+ string media_type = 9;
40+ // CreatedAt is when the upload began.
41+ google.protobuf.Timestamp created_at = 10;
42+ // ExpiresAt is when the server deletes the artifact (see
43+ // CreateJobArtifactRequest.expires_at).
44+ google.protobuf.Timestamp expires_at = 11;
45+}
46+
47+// JobArtifactService is the artifacts side of a CI runner: what ermite-runner
48+// calls when the job's code uploads or downloads artifacts through the
49+// results service the runner serves in the sandbox (Twirp
50+// github.actions.results.api.v1.ArtifactService and Azure block blobs). The
51+// runner translates that protocol; the server stores the content, enforces the
52+// limits and decides what the job may see. It is not for people: owners use
53+// ArtifactService.
54+//
55+// Every RPC needs the job token of the job ("Authorization: Bearer
56+// ermitejob_..."), and job_id must be the job of the token, with the errors of
57+// RunnerService (unauthenticated, permission_denied, and failed_precondition
58+// with a JobEnded detail once the job has ended). The run is always the one of
59+// the token: no request names a run, so a job can only reach the artifacts of
60+// its own run, and the run and job ids that the job's code puts in its Twirp
61+// requests are never passed on.
62+//
63+// An upload goes: CreateJobArtifact (a pending artifact and its id), any number
64+// of UploadJobArtifactBlock (in any order and concurrently), one
65+// CommitJobArtifact (the order of the blocks: the content), then
66+// FinalizeJobArtifact (the artifact becomes visible). Every step is safe to
67+// retry after a network error. A pending artifact that is not finalized when
68+// its job ends is deleted with its blocks.
69+//
70+// Limits are settings of the server: the size of one artifact, the space of all
71+// the artifacts of a repository (pending uploads included) and the longest
72+// retention. A call that would go over a size limit fails with
73+// resource_exhausted and a message that names the limit; nothing of the call
74+// is kept.
75+//
76+// Servers that implement this service and ArtifactService list "ci.artifacts"
77+// in GetVersionResponse.features; others answer unimplemented. A runner without
78+// that feature serves no results service, and artifact actions fail as on a
79+// GitHub Enterprise Server without artifacts v4.
80+service JobArtifactService {
81+ // CreateJobArtifact begins the upload of an artifact of the job's run and
82+ // returns its id. Fails with invalid_argument if the name or expires_at break
83+ // a rule, already_exists if an artifact of that name was finalized by a job
84+ // of the same attempt of the run (the uploader deletes it first to replace
85+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
86+ // repository already use all their space. A pending artifact of the same
87+ // job and name is discarded: a new create is a new upload.
88+ rpc CreateJobArtifact(CreateJobArtifactRequest) returns (CreateJobArtifactResponse);
89+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
90+ // client stream: the first message names the job, the artifact and the
91+ // block, every message may carry data, and the block is the data of all the
92+ // messages in order. The block is stored when the client closes the stream
93+ // and the server answers; a stream that breaks or fails stores nothing.
94+ // Storing a block id again replaces the block, so a retry is safe. Blocks
95+ // that the commit does not name are dropped.
96+ //
97+ // Fails with not_found if the artifact is not a pending artifact of this job,
98+ // failed_precondition if it is already committed, invalid_argument if a rule
99+ // of the messages is broken, and resource_exhausted if the blocks of the
100+ // artifact would exceed the size of one artifact or the space of the
101+ // repository.
102+ rpc UploadJobArtifactBlock(stream UploadJobArtifactBlockRequest) returns (UploadJobArtifactBlockResponse);
103+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
104+ // in that order (Azure's Put Block List). An empty list is an empty content
105+ // (a 0-byte upload). The server then knows the size and the digest, and
106+ // drops the blocks. A repeat of the same call after a success returns the
107+ // same result; a different list on a committed artifact is
108+ // failed_precondition. Fails with not_found if the artifact is not a pending
109+ // artifact of this job, and invalid_argument if a block id was never
110+ // uploaded or the list has more than 50,000 ids.
111+ rpc CommitJobArtifact(CommitJobArtifactRequest) returns (CommitJobArtifactResponse);
112+ // FinalizeJobArtifact makes the latest committed artifact of the job with
113+ // that name visible to the run, and returns it. An artifact of the same name
114+ // from an earlier attempt of the run is deleted at the same moment (a re-run
115+ // replaces what the first attempt uploaded). A repeat after a success
116+ // returns the same artifact. Fails with not_found if the job has no pending
117+ // artifact of that name, failed_precondition if it is not committed,
118+ // invalid_argument if digest is set and differs from the content, and
119+ // already_exists if a job of the same attempt finalized an artifact of that
120+ // name in the meantime.
121+ rpc FinalizeJobArtifact(FinalizeJobArtifactRequest) returns (FinalizeJobArtifactResponse);
122+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
123+ // all attempts, by id. An empty list is not an error.
124+ rpc ListJobArtifacts(ListJobArtifactsRequest) returns (ListJobArtifactsResponse);
125+ // DownloadJobArtifact streams the content of an artifact of the job's run.
126+ // The first message carries the artifact, then the chunks follow in order
127+ // from offset. A client that loses the stream calls again with the offset
128+ // after the last byte it received. Fails with not_found if the artifact is
129+ // not a finalized artifact of the run (or has expired), and out_of_range if
130+ // offset is beyond its size.
131+ rpc DownloadJobArtifact(DownloadJobArtifactRequest) returns (stream DownloadJobArtifactResponse);
132+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
133+ // run (upload-artifact's "overwrite", and its merge action's
134+ // "delete-merged"). Fails with not_found if there is none.
135+ rpc DeleteJobArtifact(DeleteJobArtifactRequest) returns (DeleteJobArtifactResponse);
136+}
137+
138+// CreateJobArtifactRequest begins an upload.
139+message CreateJobArtifactRequest {
140+ // JobId is the Job.job_id. It must be the job of the token.
141+ string job_id = 1;
142+ // Name is the name of the artifact, see Artifact.name.
143+ string name = 2;
144+ // MediaType is the media type of a file uploaded as is (the toolkit's
145+ // mime_type), at most 255 characters. Empty means "application/zip".
146+ string media_type = 3;
147+ // ExpiresAt is when the artifact should be deleted (the toolkit's
148+ // expires_at, from "retention-days"). Unset means the default retention of
149+ // the server. The server keeps it between one day from now and its longest
150+ // retention, and returns the time it kept.
151+ google.protobuf.Timestamp expires_at = 4;
152+}
153+
154+// CreateJobArtifactResponse identifies the pending artifact.
155+message CreateJobArtifactResponse {
156+ // ArtifactId is the id the artifact will have once finalized (Artifact.id).
157+ int64 artifact_id = 1;
158+ // ExpiresAt is the expiry the server kept.
159+ google.protobuf.Timestamp expires_at = 2;
160+}
161+
162+// UploadJobArtifactBlockRequest is one message of the stream of a block.
163+message UploadJobArtifactBlockRequest {
164+ // JobId is the Job.job_id, in the first message only. It must be the job of
165+ // the token.
166+ string job_id = 1;
167+ // ArtifactId is the CreateJobArtifactResponse.artifact_id, in the first
168+ // message only.
169+ int64 artifact_id = 2;
170+ // BlockId identifies the block within the artifact: 1 to 64 bytes (Azure's
171+ // block id, decoded from base64), in the first message only.
172+ bytes block_id = 3;
173+ // Data is the next part of the block, at most 1 MiB (1,048,576 bytes). Any
174+ // message may have none.
175+ bytes data = 4;
176+}
177+
178+// UploadJobArtifactBlockResponse confirms a stored block.
179+message UploadJobArtifactBlockResponse {
180+ // Size is the number of bytes of the block as stored.
181+ int64 size = 1;
182+}
183+
184+// CommitJobArtifactRequest sets the content of a pending artifact.
185+message CommitJobArtifactRequest {
186+ // JobId is the Job.job_id. It must be the job of the token.
187+ string job_id = 1;
188+ // ArtifactId is the CreateJobArtifactResponse.artifact_id.
189+ int64 artifact_id = 2;
190+ // BlockIds are the blocks of the content, in order, each one as given to
191+ // UploadJobArtifactBlock. At most 50,000; the same id may appear more than
192+ // once.
193+ repeated bytes block_ids = 3;
194+}
195+
196+// CommitJobArtifactResponse describes the committed content.
197+message CommitJobArtifactResponse {
198+ // Size is the number of bytes of the content.
199+ int64 size = 1;
200+ // Digest is the SHA-256 of the content, see Artifact.digest.
201+ string digest = 2;
202+}
203+
204+// FinalizeJobArtifactRequest ends an upload.
205+message FinalizeJobArtifactRequest {
206+ // JobId is the Job.job_id. It must be the job of the token.
207+ string job_id = 1;
208+ // Name is the name given to CreateJobArtifact. The toolkit finalizes by name.
209+ string name = 2;
210+ // Size is the size the uploader counted. Informational: the server keeps the
211+ // size of the content it stored, and only logs a difference.
212+ int64 size = 3;
213+ // Digest is the digest the uploader computed ("sha256:<hex>"), or empty.
214+ // When set it must be the digest of the content.
215+ string digest = 4;
216+}
217+
218+// FinalizeJobArtifactResponse describes the artifact now visible.
219+message FinalizeJobArtifactResponse {
220+ // Artifact is the finalized artifact.
221+ Artifact artifact = 1;
222+}
223+
224+// ListJobArtifactsRequest selects artifacts of the job's run.
225+message ListJobArtifactsRequest {
226+ // JobId is the Job.job_id. It must be the job of the token.
227+ string job_id = 1;
228+ // Name keeps only the artifact of that name. Empty means every name.
229+ string name = 2;
230+ // ArtifactId keeps only the artifact of that id. 0 means every id.
231+ int64 artifact_id = 3;
232+}
233+
234+// ListJobArtifactsResponse lists artifacts of the job's run.
235+message ListJobArtifactsResponse {
236+ // Artifacts are the artifacts, by id.
237+ repeated Artifact artifacts = 1;
238+}
239+
240+// DownloadJobArtifactRequest selects the content of an artifact.
241+message DownloadJobArtifactRequest {
242+ // JobId is the Job.job_id. It must be the job of the token.
243+ string job_id = 1;
244+ // ArtifactId is the Artifact.id of an artifact of the job's run.
245+ int64 artifact_id = 2;
246+ // Offset is the first byte to send, from 0 to the size of the artifact.
247+ int64 offset = 3;
248+}
249+
250+// DownloadJobArtifactResponse is one message of the content stream.
251+message DownloadJobArtifactResponse {
252+ // Artifact describes the artifact. Set in the first message of the stream
253+ // only.
254+ Artifact artifact = 1;
255+ // Chunk is the next part of the content, at most 1 MiB, in order. The first
256+ // message may have an empty chunk. The chunks of a stream add up to the size
257+ // of the artifact minus the offset.
258+ bytes chunk = 2;
259+}
260+
261+// DeleteJobArtifactRequest names the artifact to delete.
262+message DeleteJobArtifactRequest {
263+ // JobId is the Job.job_id. It must be the job of the token.
264+ string job_id = 1;
265+ // Name is the name of a finalized artifact of the job's run.
266+ string name = 2;
267+}
268+
269+// DeleteJobArtifactResponse identifies the deleted artifact.
270+message DeleteJobArtifactResponse {
271+ // ArtifactId is the Artifact.id of the artifact that was deleted.
272+ int64 artifact_id = 1;
273+}
274+
275+// ArtifactService lets people list, download and delete the artifacts of the
276+// runs of a repository (run page, `ermite run download`). Listing and
277+// downloading need a signed-in caller with read access to the repository (the
278+// repo:read scope for a token), even for a public repository: like GitHub,
279+// artifacts are not served anonymously. Deleting needs the ci:write scope. A
280+// repository or a run that does not exist or that the caller cannot read gives
281+// not_found.
282+//
283+// Artifacts are deleted when they expire (Artifact.expires_at), with their run
284+// (run retention) and with their repository.
285+//
286+// Only servers with the feature "ci.artifacts" implement it (unimplemented
287+// otherwise).
288+service ArtifactService {
289+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
290+ // artifacts of the repository use.
291+ rpc ListArtifacts(ListArtifactsRequest) returns (ListArtifactsResponse);
292+ // DownloadArtifact streams the content of an artifact, as
293+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
294+ // artifact is not an artifact of the repository, and out_of_range if offset
295+ // is beyond its size.
296+ rpc DownloadArtifact(DownloadArtifactRequest) returns (stream DownloadArtifactResponse);
297+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
298+ // artifact of the repository.
299+ rpc DeleteArtifact(DeleteArtifactRequest) returns (DeleteArtifactResponse);
300+}
301+
302+// ListArtifactsRequest selects the artifacts of a run.
303+message ListArtifactsRequest {
304+ // Repo is the repository name.
305+ string repo = 1;
306+ // RunId is the run.
307+ int64 run_id = 2;
308+}
309+
310+// ListArtifactsResponse lists the artifacts of a run.
311+message ListArtifactsResponse {
312+ // Artifacts are the artifacts of the run, all attempts, by id.
313+ repeated Artifact artifacts = 1;
314+ // UsedBytes is the space that all the artifacts of the repository use,
315+ // uploads in progress included.
316+ int64 used_bytes = 2;
317+ // QuotaBytes is the space the artifacts of the repository may use.
318+ int64 quota_bytes = 3;
319+}
320+
321+// DownloadArtifactRequest selects the content of an artifact.
322+message DownloadArtifactRequest {
323+ // Repo is the repository name.
324+ string repo = 1;
325+ // ArtifactId is the Artifact.id.
326+ int64 artifact_id = 2;
327+ // Offset is the first byte to send, from 0 to the size of the artifact.
328+ int64 offset = 3;
329+}
330+
331+// DownloadArtifactResponse is one message of the content stream.
332+message DownloadArtifactResponse {
333+ // Artifact describes the artifact. Set in the first message of the stream
334+ // only.
335+ Artifact artifact = 1;
336+ // Chunk is the next part of the content, at most 1 MiB, in order. The first
337+ // message may have an empty chunk.
338+ bytes chunk = 2;
339+}
340+
341+// DeleteArtifactRequest names the artifact to delete.
342+message DeleteArtifactRequest {
343+ // Repo is the repository name.
344+ string repo = 1;
345+ // ArtifactId is the Artifact.id.
346+ int64 artifact_id = 2;
347+}
348+
349+// DeleteArtifactResponse is empty.
350+message DeleteArtifactResponse {}
@@ -0,0 +1,350 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// Artifact is a file that a job of a run uploaded with actions/upload-artifact
10+// (artifacts v4): in most cases a zip archive, or a single file uploaded as is
11+// ("archive: false"). It belongs to a run, not to one attempt: every job of the
12+// run, in any attempt, sees the artifacts of the run. Only finalized artifacts
13+// are described; an upload in progress is invisible.
14+message Artifact {
15+ // Id identifies the artifact on the server: unique across repositories and
16+ // runs, never reused, and higher for a later artifact. It is GitHub's
17+ // "artifact id" (databaseId), the id of the REST API.
18+ int64 id = 1;
19+ // Repo is the name of the repository of the run.
20+ string repo = 2;
21+ // RunId is the run the artifact belongs to.
22+ int64 run_id = 3;
23+ // Attempt is the attempt of the run whose job uploaded it.
24+ int32 attempt = 4;
25+ // JobId is the Job.job_id of the job that uploaded it.
26+ string job_id = 5;
27+ // Name is the name of the artifact, unique among the artifacts of the run:
28+ // 1 to 256 characters of valid UTF-8, without control characters, '"', ':',
29+ // '<', '>', '|', '*', '?', '\' or '/' (the characters @actions/artifact
30+ // refuses).
31+ string name = 6;
32+ // Size is the number of bytes of the content.
33+ int64 size = 7;
34+ // Digest is the SHA-256 of the content, "sha256:" and 64 lowercase
35+ // hexadecimal characters, as GitHub gives it.
36+ string digest = 8;
37+ // MediaType is the media type of the content: "application/zip" for an
38+ // archive, the type the uploader gave for a file uploaded as is.
39+ string media_type = 9;
40+ // CreatedAt is when the upload began.
41+ google.protobuf.Timestamp created_at = 10;
42+ // ExpiresAt is when the server deletes the artifact (see
43+ // CreateJobArtifactRequest.expires_at).
44+ google.protobuf.Timestamp expires_at = 11;
45+}
46+
47+// JobArtifactService is the artifacts side of a CI runner: what ermite-runner
48+// calls when the job's code uploads or downloads artifacts through the
49+// results service the runner serves in the sandbox (Twirp
50+// github.actions.results.api.v1.ArtifactService and Azure block blobs). The
51+// runner translates that protocol; the server stores the content, enforces the
52+// limits and decides what the job may see. It is not for people: owners use
53+// ArtifactService.
54+//
55+// Every RPC needs the job token of the job ("Authorization: Bearer
56+// ermitejob_..."), and job_id must be the job of the token, with the errors of
57+// RunnerService (unauthenticated, permission_denied, and failed_precondition
58+// with a JobEnded detail once the job has ended). The run is always the one of
59+// the token: no request names a run, so a job can only reach the artifacts of
60+// its own run, and the run and job ids that the job's code puts in its Twirp
61+// requests are never passed on.
62+//
63+// An upload goes: CreateJobArtifact (a pending artifact and its id), any number
64+// of UploadJobArtifactBlock (in any order and concurrently), one
65+// CommitJobArtifact (the order of the blocks: the content), then
66+// FinalizeJobArtifact (the artifact becomes visible). Every step is safe to
67+// retry after a network error. A pending artifact that is not finalized when
68+// its job ends is deleted with its blocks.
69+//
70+// Limits are settings of the server: the size of one artifact, the space of all
71+// the artifacts of a repository (pending uploads included) and the longest
72+// retention. A call that would go over a size limit fails with
73+// resource_exhausted and a message that names the limit; nothing of the call
74+// is kept.
75+//
76+// Servers that implement this service and ArtifactService list "ci.artifacts"
77+// in GetVersionResponse.features; others answer unimplemented. A runner without
78+// that feature serves no results service, and artifact actions fail as on a
79+// GitHub Enterprise Server without artifacts v4.
80+service JobArtifactService {
81+ // CreateJobArtifact begins the upload of an artifact of the job's run and
82+ // returns its id. Fails with invalid_argument if the name or expires_at break
83+ // a rule, already_exists if an artifact of that name was finalized by a job
84+ // of the same attempt of the run (the uploader deletes it first to replace
85+ // it: DeleteJobArtifact), and resource_exhausted if the artifacts of the
86+ // repository already use all their space. A pending artifact of the same
87+ // job and name is discarded: a new create is a new upload.
88+ rpc CreateJobArtifact(CreateJobArtifactRequest) returns (CreateJobArtifactResponse);
89+ // UploadJobArtifactBlock stores one block of a pending artifact. It is a
90+ // client stream: the first message names the job, the artifact and the
91+ // block, every message may carry data, and the block is the data of all the
92+ // messages in order. The block is stored when the client closes the stream
93+ // and the server answers; a stream that breaks or fails stores nothing.
94+ // Storing a block id again replaces the block, so a retry is safe. Blocks
95+ // that the commit does not name are dropped.
96+ //
97+ // Fails with not_found if the artifact is not a pending artifact of this job,
98+ // failed_precondition if it is already committed, invalid_argument if a rule
99+ // of the messages is broken, and resource_exhausted if the blocks of the
100+ // artifact would exceed the size of one artifact or the space of the
101+ // repository.
102+ rpc UploadJobArtifactBlock(stream UploadJobArtifactBlockRequest) returns (UploadJobArtifactBlockResponse);
103+ // CommitJobArtifact sets the content of a pending artifact: the blocks named,
104+ // in that order (Azure's Put Block List). An empty list is an empty content
105+ // (a 0-byte upload). The server then knows the size and the digest, and
106+ // drops the blocks. A repeat of the same call after a success returns the
107+ // same result; a different list on a committed artifact is
108+ // failed_precondition. Fails with not_found if the artifact is not a pending
109+ // artifact of this job, and invalid_argument if a block id was never
110+ // uploaded or the list has more than 50,000 ids.
111+ rpc CommitJobArtifact(CommitJobArtifactRequest) returns (CommitJobArtifactResponse);
112+ // FinalizeJobArtifact makes the latest committed artifact of the job with
113+ // that name visible to the run, and returns it. An artifact of the same name
114+ // from an earlier attempt of the run is deleted at the same moment (a re-run
115+ // replaces what the first attempt uploaded). A repeat after a success
116+ // returns the same artifact. Fails with not_found if the job has no pending
117+ // artifact of that name, failed_precondition if it is not committed,
118+ // invalid_argument if digest is set and differs from the content, and
119+ // already_exists if a job of the same attempt finalized an artifact of that
120+ // name in the meantime.
121+ rpc FinalizeJobArtifact(FinalizeJobArtifactRequest) returns (FinalizeJobArtifactResponse);
122+ // ListJobArtifacts lists the finalized, unexpired artifacts of the job's run,
123+ // all attempts, by id. An empty list is not an error.
124+ rpc ListJobArtifacts(ListJobArtifactsRequest) returns (ListJobArtifactsResponse);
125+ // DownloadJobArtifact streams the content of an artifact of the job's run.
126+ // The first message carries the artifact, then the chunks follow in order
127+ // from offset. A client that loses the stream calls again with the offset
128+ // after the last byte it received. Fails with not_found if the artifact is
129+ // not a finalized artifact of the run (or has expired), and out_of_range if
130+ // offset is beyond its size.
131+ rpc DownloadJobArtifact(DownloadJobArtifactRequest) returns (stream DownloadJobArtifactResponse);
132+ // DeleteJobArtifact deletes the finalized artifact of that name of the job's
133+ // run (upload-artifact's "overwrite", and its merge action's
134+ // "delete-merged"). Fails with not_found if there is none.
135+ rpc DeleteJobArtifact(DeleteJobArtifactRequest) returns (DeleteJobArtifactResponse);
136+}
137+
138+// CreateJobArtifactRequest begins an upload.
139+message CreateJobArtifactRequest {
140+ // JobId is the Job.job_id. It must be the job of the token.
141+ string job_id = 1;
142+ // Name is the name of the artifact, see Artifact.name.
143+ string name = 2;
144+ // MediaType is the media type of a file uploaded as is (the toolkit's
145+ // mime_type), at most 255 characters. Empty means "application/zip".
146+ string media_type = 3;
147+ // ExpiresAt is when the artifact should be deleted (the toolkit's
148+ // expires_at, from "retention-days"). Unset means the default retention of
149+ // the server. The server keeps it between one day from now and its longest
150+ // retention, and returns the time it kept.
151+ google.protobuf.Timestamp expires_at = 4;
152+}
153+
154+// CreateJobArtifactResponse identifies the pending artifact.
155+message CreateJobArtifactResponse {
156+ // ArtifactId is the id the artifact will have once finalized (Artifact.id).
157+ int64 artifact_id = 1;
158+ // ExpiresAt is the expiry the server kept.
159+ google.protobuf.Timestamp expires_at = 2;
160+}
161+
162+// UploadJobArtifactBlockRequest is one message of the stream of a block.
163+message UploadJobArtifactBlockRequest {
164+ // JobId is the Job.job_id, in the first message only. It must be the job of
165+ // the token.
166+ string job_id = 1;
167+ // ArtifactId is the CreateJobArtifactResponse.artifact_id, in the first
168+ // message only.
169+ int64 artifact_id = 2;
170+ // BlockId identifies the block within the artifact: 1 to 64 bytes (Azure's
171+ // block id, decoded from base64), in the first message only.
172+ bytes block_id = 3;
173+ // Data is the next part of the block, at most 1 MiB (1,048,576 bytes). Any
174+ // message may have none.
175+ bytes data = 4;
176+}
177+
178+// UploadJobArtifactBlockResponse confirms a stored block.
179+message UploadJobArtifactBlockResponse {
180+ // Size is the number of bytes of the block as stored.
181+ int64 size = 1;
182+}
183+
184+// CommitJobArtifactRequest sets the content of a pending artifact.
185+message CommitJobArtifactRequest {
186+ // JobId is the Job.job_id. It must be the job of the token.
187+ string job_id = 1;
188+ // ArtifactId is the CreateJobArtifactResponse.artifact_id.
189+ int64 artifact_id = 2;
190+ // BlockIds are the blocks of the content, in order, each one as given to
191+ // UploadJobArtifactBlock. At most 50,000; the same id may appear more than
192+ // once.
193+ repeated bytes block_ids = 3;
194+}
195+
196+// CommitJobArtifactResponse describes the committed content.
197+message CommitJobArtifactResponse {
198+ // Size is the number of bytes of the content.
199+ int64 size = 1;
200+ // Digest is the SHA-256 of the content, see Artifact.digest.
201+ string digest = 2;
202+}
203+
204+// FinalizeJobArtifactRequest ends an upload.
205+message FinalizeJobArtifactRequest {
206+ // JobId is the Job.job_id. It must be the job of the token.
207+ string job_id = 1;
208+ // Name is the name given to CreateJobArtifact. The toolkit finalizes by name.
209+ string name = 2;
210+ // Size is the size the uploader counted. Informational: the server keeps the
211+ // size of the content it stored, and only logs a difference.
212+ int64 size = 3;
213+ // Digest is the digest the uploader computed ("sha256:<hex>"), or empty.
214+ // When set it must be the digest of the content.
215+ string digest = 4;
216+}
217+
218+// FinalizeJobArtifactResponse describes the artifact now visible.
219+message FinalizeJobArtifactResponse {
220+ // Artifact is the finalized artifact.
221+ Artifact artifact = 1;
222+}
223+
224+// ListJobArtifactsRequest selects artifacts of the job's run.
225+message ListJobArtifactsRequest {
226+ // JobId is the Job.job_id. It must be the job of the token.
227+ string job_id = 1;
228+ // Name keeps only the artifact of that name. Empty means every name.
229+ string name = 2;
230+ // ArtifactId keeps only the artifact of that id. 0 means every id.
231+ int64 artifact_id = 3;
232+}
233+
234+// ListJobArtifactsResponse lists artifacts of the job's run.
235+message ListJobArtifactsResponse {
236+ // Artifacts are the artifacts, by id.
237+ repeated Artifact artifacts = 1;
238+}
239+
240+// DownloadJobArtifactRequest selects the content of an artifact.
241+message DownloadJobArtifactRequest {
242+ // JobId is the Job.job_id. It must be the job of the token.
243+ string job_id = 1;
244+ // ArtifactId is the Artifact.id of an artifact of the job's run.
245+ int64 artifact_id = 2;
246+ // Offset is the first byte to send, from 0 to the size of the artifact.
247+ int64 offset = 3;
248+}
249+
250+// DownloadJobArtifactResponse is one message of the content stream.
251+message DownloadJobArtifactResponse {
252+ // Artifact describes the artifact. Set in the first message of the stream
253+ // only.
254+ Artifact artifact = 1;
255+ // Chunk is the next part of the content, at most 1 MiB, in order. The first
256+ // message may have an empty chunk. The chunks of a stream add up to the size
257+ // of the artifact minus the offset.
258+ bytes chunk = 2;
259+}
260+
261+// DeleteJobArtifactRequest names the artifact to delete.
262+message DeleteJobArtifactRequest {
263+ // JobId is the Job.job_id. It must be the job of the token.
264+ string job_id = 1;
265+ // Name is the name of a finalized artifact of the job's run.
266+ string name = 2;
267+}
268+
269+// DeleteJobArtifactResponse identifies the deleted artifact.
270+message DeleteJobArtifactResponse {
271+ // ArtifactId is the Artifact.id of the artifact that was deleted.
272+ int64 artifact_id = 1;
273+}
274+
275+// ArtifactService lets people list, download and delete the artifacts of the
276+// runs of a repository (run page, `ermite run download`). Listing and
277+// downloading need a signed-in caller with read access to the repository (the
278+// repo:read scope for a token), even for a public repository: like GitHub,
279+// artifacts are not served anonymously. Deleting needs the ci:write scope. A
280+// repository or a run that does not exist or that the caller cannot read gives
281+// not_found.
282+//
283+// Artifacts are deleted when they expire (Artifact.expires_at), with their run
284+// (run retention) and with their repository.
285+//
286+// Only servers with the feature "ci.artifacts" implement it (unimplemented
287+// otherwise).
288+service ArtifactService {
289+ // ListArtifacts lists the artifacts of a run, by id, with the space that the
290+ // artifacts of the repository use.
291+ rpc ListArtifacts(ListArtifactsRequest) returns (ListArtifactsResponse);
292+ // DownloadArtifact streams the content of an artifact, as
293+ // JobArtifactService.DownloadJobArtifact does. Fails with not_found if the
294+ // artifact is not an artifact of the repository, and out_of_range if offset
295+ // is beyond its size.
296+ rpc DownloadArtifact(DownloadArtifactRequest) returns (stream DownloadArtifactResponse);
297+ // DeleteArtifact deletes an artifact. Fails with not_found if it is not an
298+ // artifact of the repository.
299+ rpc DeleteArtifact(DeleteArtifactRequest) returns (DeleteArtifactResponse);
300+}
301+
302+// ListArtifactsRequest selects the artifacts of a run.
303+message ListArtifactsRequest {
304+ // Repo is the repository name.
305+ string repo = 1;
306+ // RunId is the run.
307+ int64 run_id = 2;
308+}
309+
310+// ListArtifactsResponse lists the artifacts of a run.
311+message ListArtifactsResponse {
312+ // Artifacts are the artifacts of the run, all attempts, by id.
313+ repeated Artifact artifacts = 1;
314+ // UsedBytes is the space that all the artifacts of the repository use,
315+ // uploads in progress included.
316+ int64 used_bytes = 2;
317+ // QuotaBytes is the space the artifacts of the repository may use.
318+ int64 quota_bytes = 3;
319+}
320+
321+// DownloadArtifactRequest selects the content of an artifact.
322+message DownloadArtifactRequest {
323+ // Repo is the repository name.
324+ string repo = 1;
325+ // ArtifactId is the Artifact.id.
326+ int64 artifact_id = 2;
327+ // Offset is the first byte to send, from 0 to the size of the artifact.
328+ int64 offset = 3;
329+}
330+
331+// DownloadArtifactResponse is one message of the content stream.
332+message DownloadArtifactResponse {
333+ // Artifact describes the artifact. Set in the first message of the stream
334+ // only.
335+ Artifact artifact = 1;
336+ // Chunk is the next part of the content, at most 1 MiB, in order. The first
337+ // message may have an empty chunk.
338+ bytes chunk = 2;
339+}
340+
341+// DeleteArtifactRequest names the artifact to delete.
342+message DeleteArtifactRequest {
343+ // Repo is the repository name.
344+ string repo = 1;
345+ // ArtifactId is the Artifact.id.
346+ int64 artifact_id = 2;
347+}
348+
349+// DeleteArtifactResponse is empty.
350+message DeleteArtifactResponse {}
M proto/ermite/server.proto +2 -1
@@ -43,7 +43,8 @@ message GetVersionResponse {
4343 // (SecretService, VariableService and EnvironmentService, and in Job the
4444 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
4545 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
46- // (CI_STATUS_PENDING and RunJob.concurrency_group) and "ci.cache" (Job.cache).
46+ // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
47+ // and "ci.artifacts" (JobArtifactService and ArtifactService).
4748 repeated string features = 2;
4849 }
4950
@@ -43,7 +43,8 @@ message GetVersionResponse {
43 // (SecretService, VariableService and EnvironmentService, and in Job the43 // (SecretService, VariableService and EnvironmentService, and in Job the
44 // secrets, vars, environment, permissions and trusted fields and Run.trusted),44 // secrets, vars, environment, permissions and trusted fields and Run.trusted),
45 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"45 // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency"
46- // (CI_STATUS_PENDING and RunJob.concurrency_group) and "ci.cache" (Job.cache).46+ // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache)
47+ // and "ci.artifacts" (JobArtifactService and ArtifactService).
47 repeated string features = 2;48 repeated string features = 2;
48 }49 }
49 50

Keyboard shortcuts

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

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

Branches 2

Tags 18

↑↓ moveenter openesc close