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 Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown
7 files · +2446 -4
M README.md +4 -2
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 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 | 7 | ```go | |
| 8 | 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 | 27 | | `SecretService` | `SetSecret`, `ListSecrets` (names only, never values), `DeleteSecret`; scopes instance, repository and environment | | |
| 28 | 28 | | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`; same scopes | | |
| 29 | 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 | 32 | | `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) | | |
| 31 | 33 | ||
| 32 | 34 | Tokens carry scopes only; presets such as `cli` or `agent` are a client concern. | |
| 33 | 35 | ||
| 34 | 36 | ## 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 | 39 | - `gen/ermite/`: generated Go messages. `gen/ermite/ermiteconnect/`: generated Connect clients and handlers. Committed, never edited by hand. | |
| 38 | 40 | ||
| 39 | 41 | ## 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 | ```go | 7 | ```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 | ## Layout | 36 | ## 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 | ## Generate | 41 | ## 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
| @@ -83,7 +83,8 @@ type GetVersionResponse struct { | |||
| 83 | 83 | // (SecretService, VariableService and EnvironmentService, and in Job the | |
| 84 | 84 | // secrets, vars, environment, permissions and trusted fields and Run.trusted), | |
| 85 | 85 | // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency" | |
| 86 | - | // (CI_STATUS_PENDING and RunJob.concurrency_group) and "ci.cache" (Job.cache). | |
| 86 | + | // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache) | |
| 87 | + | // and "ci.artifacts" (JobArtifactService and ArtifactService). | |
| 87 | 88 | Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` | |
| 88 | 89 | unknownFields protoimpl.UnknownFields | |
| 89 | 90 | sizeCache protoimpl.SizeCache | |
| @@ -83,7 +83,8 @@ type GetVersionResponse struct { | |||||
| 83 | // (SecretService, VariableService and EnvironmentService, and in Job the | 83 | // (SecretService, VariableService and EnvironmentService, and in Job the | ||
| 84 | // secrets, vars, environment, permissions and trusted fields and Run.trusted), | 84 | // secrets, vars, environment, permissions and trusted fields and Run.trusted), | ||
| 85 | // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency" | 85 | // "ci.dispatch" (RunService.DispatchWorkflow), "ci.concurrency" | ||
| 86 | - | // (CI_STATUS_PENDING and RunJob.concurrency_group) and "ci.cache" (Job.cache). | 86 | + | // (CI_STATUS_PENDING and RunJob.concurrency_group), "ci.cache" (Job.cache) |
| 87 | + | // and "ci.artifacts" (JobArtifactService and ArtifactService). | |||
| 87 | Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` | 88 | Features []string `protobuf:"bytes,2,rep,name=features,proto3" json:"features,omitempty"` | ||
| 88 | unknownFields protoimpl.UnknownFields | 89 | unknownFields protoimpl.UnknownFields | ||
| 89 | sizeCache protoimpl.SizeCache | 90 | sizeCache protoimpl.SizeCache | ||
M gen_test.go +10 -0
| @@ -78,6 +78,16 @@ func TestProcedures(t *testing.T) { | |||
| 78 | 78 | {ermiteconnect.EnvironmentServiceCreateEnvironmentProcedure, "/ermite.EnvironmentService/CreateEnvironment"}, | |
| 79 | 79 | {ermiteconnect.EnvironmentServiceListEnvironmentsProcedure, "/ermite.EnvironmentService/ListEnvironments"}, | |
| 80 | 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 | 92 | for _, tt := range tests { | |
| 83 | 93 | 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 { | |||
| 43 | 43 | // (SecretService, VariableService and EnvironmentService, and in Job the | |
| 44 | 44 | // secrets, vars, environment, permissions and trusted fields and Run.trusted), | |
| 45 | 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 | 48 | repeated string features = 2; | |
| 48 | 49 | } | |
| 49 | 50 | ||
| @@ -43,7 +43,8 @@ message GetVersionResponse { | |||||
| 43 | // (SecretService, VariableService and EnvironmentService, and in Job the | 43 | // (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 | ||||