detached 6cfb42d

feat(proto): add the mirror service and mirror events

commit
6cfb42df17f633480c92cdd239b33257c5f674ba
author
brice · · 2026-10-20 05:26 UTC
committer
brice · · 2026-10-20 07:06 UTC
parent
94ffd87
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +2833 -41
M docs/services.md +32 -0
@@ -18,3 +18,35 @@
1818 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
1919 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact` |
2020 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`. Scoped to the run of the job token |
21+
22+## MirrorService
23+
24+Servers implementing push mirrors advertise `mirror` in `GetVersionResponse.features`.
25+No new token scope is introduced.
26+
27+| RPC | Access and behavior |
28+|---|---|
29+| `GetMirrorKey` | Non-anonymous caller; generates the public SSH key on first call. Without `ERMITE_SECRETS_KEY`, returns `failed_precondition` |
30+| `RotateMirrorKey` | Owner rights: admin token or a token with every scope and no repository or branch limits. Replaces the key immediately and returns the previous fingerprint |
31+| `GetMirrorConfig`, `SetMirrorConfig` | Owner rights. Set patches only present fields; invalid fields return `invalid_argument` with field violations |
32+| `SetRepoMirror` | `repo:admin` on the repository. Sets an override, even if global mirroring is disabled; empty cron and absent all-branches inherit the global values |
33+| `RemoveRepoMirror` | `repo:admin` on the repository. Removes the override |
34+| `SetRepoMirrorExcluded` | `repo:admin` on the repository. Opts out of or into the global template; a URL override wins over exclusion |
35+| `GetMirrorStatus` | Read access to the repository. Error `detail` is returned only with `repo:admin` |
36+| `ListMirrorStatus` | Owner rights |
37+| `SyncMirror` | `repo:admin` for `repo`, or owner rights for `all=true` (with empty `repo`). Returns per-repository results, including queued and no-op indicators |
38+
39+Cron expressions have five fields, run in UTC and must be at least five minutes
40+apart. Mirrors push the default branch (or every branch with `all_branches`) and
41+tags. `force` lifts a pause, skips the no-op check and the 60-second manual sync
42+throttle; it **never** performs a Git force push. Pushes are atomic: one rejected
43+ref fails the whole push. After key rotation, add the new public key to remotes
44+and sync with `force` to recover from authentication failures. Configuration can
45+be saved without `ERMITE_SECRETS_KEY`, but pushes cannot run without it.
46+
47+`EventService.Watch` adds `mirror_failed` (field 23, `MirrorFailed`) when a mirror
48+enters failing or paused, including failing to paused, and `mirror_recovered`
49+(field 24, `MirrorRecovered`) when a failing or paused mirror succeeds again.
50+These are transition events, not events for every attempt. They are delivered on
51+`repo:<name>` and `home`, subject to repository visibility. Neither event carries
52+a URL or error detail.
@@ -18,3 +18,35 @@
18 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |18 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
19 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact` |19 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact` |
20 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`. Scoped to the run of the job token |20 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`. Scoped to the run of the job token |
21+
22+## MirrorService
23+
24+Servers implementing push mirrors advertise `mirror` in `GetVersionResponse.features`.
25+No new token scope is introduced.
26+
27+| RPC | Access and behavior |
28+|---|---|
29+| `GetMirrorKey` | Non-anonymous caller; generates the public SSH key on first call. Without `ERMITE_SECRETS_KEY`, returns `failed_precondition` |
30+| `RotateMirrorKey` | Owner rights: admin token or a token with every scope and no repository or branch limits. Replaces the key immediately and returns the previous fingerprint |
31+| `GetMirrorConfig`, `SetMirrorConfig` | Owner rights. Set patches only present fields; invalid fields return `invalid_argument` with field violations |
32+| `SetRepoMirror` | `repo:admin` on the repository. Sets an override, even if global mirroring is disabled; empty cron and absent all-branches inherit the global values |
33+| `RemoveRepoMirror` | `repo:admin` on the repository. Removes the override |
34+| `SetRepoMirrorExcluded` | `repo:admin` on the repository. Opts out of or into the global template; a URL override wins over exclusion |
35+| `GetMirrorStatus` | Read access to the repository. Error `detail` is returned only with `repo:admin` |
36+| `ListMirrorStatus` | Owner rights |
37+| `SyncMirror` | `repo:admin` for `repo`, or owner rights for `all=true` (with empty `repo`). Returns per-repository results, including queued and no-op indicators |
38+
39+Cron expressions have five fields, run in UTC and must be at least five minutes
40+apart. Mirrors push the default branch (or every branch with `all_branches`) and
41+tags. `force` lifts a pause, skips the no-op check and the 60-second manual sync
42+throttle; it **never** performs a Git force push. Pushes are atomic: one rejected
43+ref fails the whole push. After key rotation, add the new public key to remotes
44+and sync with `force` to recover from authentication failures. Configuration can
45+be saved without `ERMITE_SECRETS_KEY`, but pushes cannot run without it.
46+
47+`EventService.Watch` adds `mirror_failed` (field 23, `MirrorFailed`) when a mirror
48+enters failing or paused, including failing to paused, and `mirror_recovered`
49+(field 24, `MirrorRecovered`) when a failing or paused mirror succeeds again.
50+These are transition events, not events for every attempt. They are delivered on
51+`repo:<name>` and `home`, subject to repository visibility. Neither event carries
52+a URL or error detail.
A gen/ermite/ermiteconnect/mirror.connect.go +404 -0
@@ -0,0 +1,404 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/mirror.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+ // MirrorServiceName is the fully-qualified name of the MirrorService service.
27+ MirrorServiceName = "ermite.MirrorService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // MirrorServiceGetMirrorKeyProcedure is the fully-qualified name of the MirrorService's
39+ // GetMirrorKey RPC.
40+ MirrorServiceGetMirrorKeyProcedure = "/ermite.MirrorService/GetMirrorKey"
41+ // MirrorServiceRotateMirrorKeyProcedure is the fully-qualified name of the MirrorService's
42+ // RotateMirrorKey RPC.
43+ MirrorServiceRotateMirrorKeyProcedure = "/ermite.MirrorService/RotateMirrorKey"
44+ // MirrorServiceGetMirrorConfigProcedure is the fully-qualified name of the MirrorService's
45+ // GetMirrorConfig RPC.
46+ MirrorServiceGetMirrorConfigProcedure = "/ermite.MirrorService/GetMirrorConfig"
47+ // MirrorServiceSetMirrorConfigProcedure is the fully-qualified name of the MirrorService's
48+ // SetMirrorConfig RPC.
49+ MirrorServiceSetMirrorConfigProcedure = "/ermite.MirrorService/SetMirrorConfig"
50+ // MirrorServiceSetRepoMirrorProcedure is the fully-qualified name of the MirrorService's
51+ // SetRepoMirror RPC.
52+ MirrorServiceSetRepoMirrorProcedure = "/ermite.MirrorService/SetRepoMirror"
53+ // MirrorServiceRemoveRepoMirrorProcedure is the fully-qualified name of the MirrorService's
54+ // RemoveRepoMirror RPC.
55+ MirrorServiceRemoveRepoMirrorProcedure = "/ermite.MirrorService/RemoveRepoMirror"
56+ // MirrorServiceSetRepoMirrorExcludedProcedure is the fully-qualified name of the MirrorService's
57+ // SetRepoMirrorExcluded RPC.
58+ MirrorServiceSetRepoMirrorExcludedProcedure = "/ermite.MirrorService/SetRepoMirrorExcluded"
59+ // MirrorServiceGetMirrorStatusProcedure is the fully-qualified name of the MirrorService's
60+ // GetMirrorStatus RPC.
61+ MirrorServiceGetMirrorStatusProcedure = "/ermite.MirrorService/GetMirrorStatus"
62+ // MirrorServiceListMirrorStatusProcedure is the fully-qualified name of the MirrorService's
63+ // ListMirrorStatus RPC.
64+ MirrorServiceListMirrorStatusProcedure = "/ermite.MirrorService/ListMirrorStatus"
65+ // MirrorServiceSyncMirrorProcedure is the fully-qualified name of the MirrorService's SyncMirror
66+ // RPC.
67+ MirrorServiceSyncMirrorProcedure = "/ermite.MirrorService/SyncMirror"
68+)
69+
70+// MirrorServiceClient is a client for the ermite.MirrorService service.
71+type MirrorServiceClient interface {
72+ // GetMirrorKey returns the public mirror key, generating it on first use.
73+ // Needs a non-anonymous caller. Fails with failed_precondition without
74+ // ERMITE_SECRETS_KEY.
75+ GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error)
76+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
77+ // and sync with force to recover from authentication failures.
78+ RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error)
79+ // GetMirrorConfig returns the global mirror policy.
80+ GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error)
81+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
82+ // Invalid fields give invalid_argument with field violations.
83+ SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error)
84+ // SetRepoMirror sets an override, even when global mirroring is disabled.
85+ SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error)
86+ // RemoveRepoMirror removes the override so the global policy applies again.
87+ RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error)
88+ // SetRepoMirrorExcluded excludes or includes a repository in the global
89+ // policy. An explicit URL override takes precedence.
90+ SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error)
91+ // GetMirrorStatus returns the resolved target and status of one repository.
92+ GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error)
93+ // ListMirrorStatus lists mirror statuses and needs owner rights.
94+ ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error)
95+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
96+ // Force. Manual requests without Force are at least 60 seconds apart.
97+ SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error)
98+}
99+
100+// NewMirrorServiceClient constructs a client for the ermite.MirrorService service. By default, it
101+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
102+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
103+// connect.WithGRPCWeb() options.
104+//
105+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
106+// http://api.acme.com or https://acme.com/grpc).
107+func NewMirrorServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MirrorServiceClient {
108+ baseURL = strings.TrimRight(baseURL, "/")
109+ mirrorServiceMethods := ermite.File_ermite_mirror_proto.Services().ByName("MirrorService").Methods()
110+ return &mirrorServiceClient{
111+ getMirrorKey: connect.NewClient[ermite.GetMirrorKeyRequest, ermite.GetMirrorKeyResponse](
112+ httpClient,
113+ baseURL+MirrorServiceGetMirrorKeyProcedure,
114+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorKey")),
115+ connect.WithClientOptions(opts...),
116+ ),
117+ rotateMirrorKey: connect.NewClient[ermite.RotateMirrorKeyRequest, ermite.RotateMirrorKeyResponse](
118+ httpClient,
119+ baseURL+MirrorServiceRotateMirrorKeyProcedure,
120+ connect.WithSchema(mirrorServiceMethods.ByName("RotateMirrorKey")),
121+ connect.WithClientOptions(opts...),
122+ ),
123+ getMirrorConfig: connect.NewClient[ermite.GetMirrorConfigRequest, ermite.GetMirrorConfigResponse](
124+ httpClient,
125+ baseURL+MirrorServiceGetMirrorConfigProcedure,
126+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorConfig")),
127+ connect.WithClientOptions(opts...),
128+ ),
129+ setMirrorConfig: connect.NewClient[ermite.SetMirrorConfigRequest, ermite.SetMirrorConfigResponse](
130+ httpClient,
131+ baseURL+MirrorServiceSetMirrorConfigProcedure,
132+ connect.WithSchema(mirrorServiceMethods.ByName("SetMirrorConfig")),
133+ connect.WithClientOptions(opts...),
134+ ),
135+ setRepoMirror: connect.NewClient[ermite.SetRepoMirrorRequest, ermite.SetRepoMirrorResponse](
136+ httpClient,
137+ baseURL+MirrorServiceSetRepoMirrorProcedure,
138+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirror")),
139+ connect.WithClientOptions(opts...),
140+ ),
141+ removeRepoMirror: connect.NewClient[ermite.RemoveRepoMirrorRequest, ermite.RemoveRepoMirrorResponse](
142+ httpClient,
143+ baseURL+MirrorServiceRemoveRepoMirrorProcedure,
144+ connect.WithSchema(mirrorServiceMethods.ByName("RemoveRepoMirror")),
145+ connect.WithClientOptions(opts...),
146+ ),
147+ setRepoMirrorExcluded: connect.NewClient[ermite.SetRepoMirrorExcludedRequest, ermite.SetRepoMirrorExcludedResponse](
148+ httpClient,
149+ baseURL+MirrorServiceSetRepoMirrorExcludedProcedure,
150+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirrorExcluded")),
151+ connect.WithClientOptions(opts...),
152+ ),
153+ getMirrorStatus: connect.NewClient[ermite.GetMirrorStatusRequest, ermite.GetMirrorStatusResponse](
154+ httpClient,
155+ baseURL+MirrorServiceGetMirrorStatusProcedure,
156+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorStatus")),
157+ connect.WithClientOptions(opts...),
158+ ),
159+ listMirrorStatus: connect.NewClient[ermite.ListMirrorStatusRequest, ermite.ListMirrorStatusResponse](
160+ httpClient,
161+ baseURL+MirrorServiceListMirrorStatusProcedure,
162+ connect.WithSchema(mirrorServiceMethods.ByName("ListMirrorStatus")),
163+ connect.WithClientOptions(opts...),
164+ ),
165+ syncMirror: connect.NewClient[ermite.SyncMirrorRequest, ermite.SyncMirrorResponse](
166+ httpClient,
167+ baseURL+MirrorServiceSyncMirrorProcedure,
168+ connect.WithSchema(mirrorServiceMethods.ByName("SyncMirror")),
169+ connect.WithClientOptions(opts...),
170+ ),
171+ }
172+}
173+
174+// mirrorServiceClient implements MirrorServiceClient.
175+type mirrorServiceClient struct {
176+ getMirrorKey *connect.Client[ermite.GetMirrorKeyRequest, ermite.GetMirrorKeyResponse]
177+ rotateMirrorKey *connect.Client[ermite.RotateMirrorKeyRequest, ermite.RotateMirrorKeyResponse]
178+ getMirrorConfig *connect.Client[ermite.GetMirrorConfigRequest, ermite.GetMirrorConfigResponse]
179+ setMirrorConfig *connect.Client[ermite.SetMirrorConfigRequest, ermite.SetMirrorConfigResponse]
180+ setRepoMirror *connect.Client[ermite.SetRepoMirrorRequest, ermite.SetRepoMirrorResponse]
181+ removeRepoMirror *connect.Client[ermite.RemoveRepoMirrorRequest, ermite.RemoveRepoMirrorResponse]
182+ setRepoMirrorExcluded *connect.Client[ermite.SetRepoMirrorExcludedRequest, ermite.SetRepoMirrorExcludedResponse]
183+ getMirrorStatus *connect.Client[ermite.GetMirrorStatusRequest, ermite.GetMirrorStatusResponse]
184+ listMirrorStatus *connect.Client[ermite.ListMirrorStatusRequest, ermite.ListMirrorStatusResponse]
185+ syncMirror *connect.Client[ermite.SyncMirrorRequest, ermite.SyncMirrorResponse]
186+}
187+
188+// GetMirrorKey calls ermite.MirrorService.GetMirrorKey.
189+func (c *mirrorServiceClient) GetMirrorKey(ctx context.Context, req *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error) {
190+ return c.getMirrorKey.CallUnary(ctx, req)
191+}
192+
193+// RotateMirrorKey calls ermite.MirrorService.RotateMirrorKey.
194+func (c *mirrorServiceClient) RotateMirrorKey(ctx context.Context, req *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error) {
195+ return c.rotateMirrorKey.CallUnary(ctx, req)
196+}
197+
198+// GetMirrorConfig calls ermite.MirrorService.GetMirrorConfig.
199+func (c *mirrorServiceClient) GetMirrorConfig(ctx context.Context, req *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error) {
200+ return c.getMirrorConfig.CallUnary(ctx, req)
201+}
202+
203+// SetMirrorConfig calls ermite.MirrorService.SetMirrorConfig.
204+func (c *mirrorServiceClient) SetMirrorConfig(ctx context.Context, req *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error) {
205+ return c.setMirrorConfig.CallUnary(ctx, req)
206+}
207+
208+// SetRepoMirror calls ermite.MirrorService.SetRepoMirror.
209+func (c *mirrorServiceClient) SetRepoMirror(ctx context.Context, req *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error) {
210+ return c.setRepoMirror.CallUnary(ctx, req)
211+}
212+
213+// RemoveRepoMirror calls ermite.MirrorService.RemoveRepoMirror.
214+func (c *mirrorServiceClient) RemoveRepoMirror(ctx context.Context, req *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error) {
215+ return c.removeRepoMirror.CallUnary(ctx, req)
216+}
217+
218+// SetRepoMirrorExcluded calls ermite.MirrorService.SetRepoMirrorExcluded.
219+func (c *mirrorServiceClient) SetRepoMirrorExcluded(ctx context.Context, req *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error) {
220+ return c.setRepoMirrorExcluded.CallUnary(ctx, req)
221+}
222+
223+// GetMirrorStatus calls ermite.MirrorService.GetMirrorStatus.
224+func (c *mirrorServiceClient) GetMirrorStatus(ctx context.Context, req *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error) {
225+ return c.getMirrorStatus.CallUnary(ctx, req)
226+}
227+
228+// ListMirrorStatus calls ermite.MirrorService.ListMirrorStatus.
229+func (c *mirrorServiceClient) ListMirrorStatus(ctx context.Context, req *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error) {
230+ return c.listMirrorStatus.CallUnary(ctx, req)
231+}
232+
233+// SyncMirror calls ermite.MirrorService.SyncMirror.
234+func (c *mirrorServiceClient) SyncMirror(ctx context.Context, req *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error) {
235+ return c.syncMirror.CallUnary(ctx, req)
236+}
237+
238+// MirrorServiceHandler is an implementation of the ermite.MirrorService service.
239+type MirrorServiceHandler interface {
240+ // GetMirrorKey returns the public mirror key, generating it on first use.
241+ // Needs a non-anonymous caller. Fails with failed_precondition without
242+ // ERMITE_SECRETS_KEY.
243+ GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error)
244+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
245+ // and sync with force to recover from authentication failures.
246+ RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error)
247+ // GetMirrorConfig returns the global mirror policy.
248+ GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error)
249+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
250+ // Invalid fields give invalid_argument with field violations.
251+ SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error)
252+ // SetRepoMirror sets an override, even when global mirroring is disabled.
253+ SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error)
254+ // RemoveRepoMirror removes the override so the global policy applies again.
255+ RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error)
256+ // SetRepoMirrorExcluded excludes or includes a repository in the global
257+ // policy. An explicit URL override takes precedence.
258+ SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error)
259+ // GetMirrorStatus returns the resolved target and status of one repository.
260+ GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error)
261+ // ListMirrorStatus lists mirror statuses and needs owner rights.
262+ ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error)
263+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
264+ // Force. Manual requests without Force are at least 60 seconds apart.
265+ SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error)
266+}
267+
268+// NewMirrorServiceHandler builds an HTTP handler from the service implementation. It returns the
269+// path on which to mount the handler and the handler itself.
270+//
271+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
272+// and JSON codecs. They also support gzip compression.
273+func NewMirrorServiceHandler(svc MirrorServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
274+ mirrorServiceMethods := ermite.File_ermite_mirror_proto.Services().ByName("MirrorService").Methods()
275+ mirrorServiceGetMirrorKeyHandler := connect.NewUnaryHandler(
276+ MirrorServiceGetMirrorKeyProcedure,
277+ svc.GetMirrorKey,
278+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorKey")),
279+ connect.WithHandlerOptions(opts...),
280+ )
281+ mirrorServiceRotateMirrorKeyHandler := connect.NewUnaryHandler(
282+ MirrorServiceRotateMirrorKeyProcedure,
283+ svc.RotateMirrorKey,
284+ connect.WithSchema(mirrorServiceMethods.ByName("RotateMirrorKey")),
285+ connect.WithHandlerOptions(opts...),
286+ )
287+ mirrorServiceGetMirrorConfigHandler := connect.NewUnaryHandler(
288+ MirrorServiceGetMirrorConfigProcedure,
289+ svc.GetMirrorConfig,
290+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorConfig")),
291+ connect.WithHandlerOptions(opts...),
292+ )
293+ mirrorServiceSetMirrorConfigHandler := connect.NewUnaryHandler(
294+ MirrorServiceSetMirrorConfigProcedure,
295+ svc.SetMirrorConfig,
296+ connect.WithSchema(mirrorServiceMethods.ByName("SetMirrorConfig")),
297+ connect.WithHandlerOptions(opts...),
298+ )
299+ mirrorServiceSetRepoMirrorHandler := connect.NewUnaryHandler(
300+ MirrorServiceSetRepoMirrorProcedure,
301+ svc.SetRepoMirror,
302+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirror")),
303+ connect.WithHandlerOptions(opts...),
304+ )
305+ mirrorServiceRemoveRepoMirrorHandler := connect.NewUnaryHandler(
306+ MirrorServiceRemoveRepoMirrorProcedure,
307+ svc.RemoveRepoMirror,
308+ connect.WithSchema(mirrorServiceMethods.ByName("RemoveRepoMirror")),
309+ connect.WithHandlerOptions(opts...),
310+ )
311+ mirrorServiceSetRepoMirrorExcludedHandler := connect.NewUnaryHandler(
312+ MirrorServiceSetRepoMirrorExcludedProcedure,
313+ svc.SetRepoMirrorExcluded,
314+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirrorExcluded")),
315+ connect.WithHandlerOptions(opts...),
316+ )
317+ mirrorServiceGetMirrorStatusHandler := connect.NewUnaryHandler(
318+ MirrorServiceGetMirrorStatusProcedure,
319+ svc.GetMirrorStatus,
320+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorStatus")),
321+ connect.WithHandlerOptions(opts...),
322+ )
323+ mirrorServiceListMirrorStatusHandler := connect.NewUnaryHandler(
324+ MirrorServiceListMirrorStatusProcedure,
325+ svc.ListMirrorStatus,
326+ connect.WithSchema(mirrorServiceMethods.ByName("ListMirrorStatus")),
327+ connect.WithHandlerOptions(opts...),
328+ )
329+ mirrorServiceSyncMirrorHandler := connect.NewUnaryHandler(
330+ MirrorServiceSyncMirrorProcedure,
331+ svc.SyncMirror,
332+ connect.WithSchema(mirrorServiceMethods.ByName("SyncMirror")),
333+ connect.WithHandlerOptions(opts...),
334+ )
335+ return "/ermite.MirrorService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
336+ switch r.URL.Path {
337+ case MirrorServiceGetMirrorKeyProcedure:
338+ mirrorServiceGetMirrorKeyHandler.ServeHTTP(w, r)
339+ case MirrorServiceRotateMirrorKeyProcedure:
340+ mirrorServiceRotateMirrorKeyHandler.ServeHTTP(w, r)
341+ case MirrorServiceGetMirrorConfigProcedure:
342+ mirrorServiceGetMirrorConfigHandler.ServeHTTP(w, r)
343+ case MirrorServiceSetMirrorConfigProcedure:
344+ mirrorServiceSetMirrorConfigHandler.ServeHTTP(w, r)
345+ case MirrorServiceSetRepoMirrorProcedure:
346+ mirrorServiceSetRepoMirrorHandler.ServeHTTP(w, r)
347+ case MirrorServiceRemoveRepoMirrorProcedure:
348+ mirrorServiceRemoveRepoMirrorHandler.ServeHTTP(w, r)
349+ case MirrorServiceSetRepoMirrorExcludedProcedure:
350+ mirrorServiceSetRepoMirrorExcludedHandler.ServeHTTP(w, r)
351+ case MirrorServiceGetMirrorStatusProcedure:
352+ mirrorServiceGetMirrorStatusHandler.ServeHTTP(w, r)
353+ case MirrorServiceListMirrorStatusProcedure:
354+ mirrorServiceListMirrorStatusHandler.ServeHTTP(w, r)
355+ case MirrorServiceSyncMirrorProcedure:
356+ mirrorServiceSyncMirrorHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedMirrorServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedMirrorServiceHandler struct{}
365+
366+func (UnimplementedMirrorServiceHandler) GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorKey is not implemented"))
368+}
369+
370+func (UnimplementedMirrorServiceHandler) RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.RotateMirrorKey is not implemented"))
372+}
373+
374+func (UnimplementedMirrorServiceHandler) GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorConfig is not implemented"))
376+}
377+
378+func (UnimplementedMirrorServiceHandler) SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetMirrorConfig is not implemented"))
380+}
381+
382+func (UnimplementedMirrorServiceHandler) SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetRepoMirror is not implemented"))
384+}
385+
386+func (UnimplementedMirrorServiceHandler) RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error) {
387+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.RemoveRepoMirror is not implemented"))
388+}
389+
390+func (UnimplementedMirrorServiceHandler) SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetRepoMirrorExcluded is not implemented"))
392+}
393+
394+func (UnimplementedMirrorServiceHandler) GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error) {
395+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorStatus is not implemented"))
396+}
397+
398+func (UnimplementedMirrorServiceHandler) ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error) {
399+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.ListMirrorStatus is not implemented"))
400+}
401+
402+func (UnimplementedMirrorServiceHandler) SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error) {
403+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SyncMirror is not implemented"))
404+}
@@ -0,0 +1,404 @@
1+// SPDX-License-Identifier: MIT
2+
3+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
4+//
5+// Source: ermite/mirror.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+ // MirrorServiceName is the fully-qualified name of the MirrorService service.
27+ MirrorServiceName = "ermite.MirrorService"
28+)
29+
30+// These constants are the fully-qualified names of the RPCs defined in this package. They're
31+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
32+//
33+// Note that these are different from the fully-qualified method names used by
34+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
35+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
36+// period.
37+const (
38+ // MirrorServiceGetMirrorKeyProcedure is the fully-qualified name of the MirrorService's
39+ // GetMirrorKey RPC.
40+ MirrorServiceGetMirrorKeyProcedure = "/ermite.MirrorService/GetMirrorKey"
41+ // MirrorServiceRotateMirrorKeyProcedure is the fully-qualified name of the MirrorService's
42+ // RotateMirrorKey RPC.
43+ MirrorServiceRotateMirrorKeyProcedure = "/ermite.MirrorService/RotateMirrorKey"
44+ // MirrorServiceGetMirrorConfigProcedure is the fully-qualified name of the MirrorService's
45+ // GetMirrorConfig RPC.
46+ MirrorServiceGetMirrorConfigProcedure = "/ermite.MirrorService/GetMirrorConfig"
47+ // MirrorServiceSetMirrorConfigProcedure is the fully-qualified name of the MirrorService's
48+ // SetMirrorConfig RPC.
49+ MirrorServiceSetMirrorConfigProcedure = "/ermite.MirrorService/SetMirrorConfig"
50+ // MirrorServiceSetRepoMirrorProcedure is the fully-qualified name of the MirrorService's
51+ // SetRepoMirror RPC.
52+ MirrorServiceSetRepoMirrorProcedure = "/ermite.MirrorService/SetRepoMirror"
53+ // MirrorServiceRemoveRepoMirrorProcedure is the fully-qualified name of the MirrorService's
54+ // RemoveRepoMirror RPC.
55+ MirrorServiceRemoveRepoMirrorProcedure = "/ermite.MirrorService/RemoveRepoMirror"
56+ // MirrorServiceSetRepoMirrorExcludedProcedure is the fully-qualified name of the MirrorService's
57+ // SetRepoMirrorExcluded RPC.
58+ MirrorServiceSetRepoMirrorExcludedProcedure = "/ermite.MirrorService/SetRepoMirrorExcluded"
59+ // MirrorServiceGetMirrorStatusProcedure is the fully-qualified name of the MirrorService's
60+ // GetMirrorStatus RPC.
61+ MirrorServiceGetMirrorStatusProcedure = "/ermite.MirrorService/GetMirrorStatus"
62+ // MirrorServiceListMirrorStatusProcedure is the fully-qualified name of the MirrorService's
63+ // ListMirrorStatus RPC.
64+ MirrorServiceListMirrorStatusProcedure = "/ermite.MirrorService/ListMirrorStatus"
65+ // MirrorServiceSyncMirrorProcedure is the fully-qualified name of the MirrorService's SyncMirror
66+ // RPC.
67+ MirrorServiceSyncMirrorProcedure = "/ermite.MirrorService/SyncMirror"
68+)
69+
70+// MirrorServiceClient is a client for the ermite.MirrorService service.
71+type MirrorServiceClient interface {
72+ // GetMirrorKey returns the public mirror key, generating it on first use.
73+ // Needs a non-anonymous caller. Fails with failed_precondition without
74+ // ERMITE_SECRETS_KEY.
75+ GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error)
76+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
77+ // and sync with force to recover from authentication failures.
78+ RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error)
79+ // GetMirrorConfig returns the global mirror policy.
80+ GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error)
81+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
82+ // Invalid fields give invalid_argument with field violations.
83+ SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error)
84+ // SetRepoMirror sets an override, even when global mirroring is disabled.
85+ SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error)
86+ // RemoveRepoMirror removes the override so the global policy applies again.
87+ RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error)
88+ // SetRepoMirrorExcluded excludes or includes a repository in the global
89+ // policy. An explicit URL override takes precedence.
90+ SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error)
91+ // GetMirrorStatus returns the resolved target and status of one repository.
92+ GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error)
93+ // ListMirrorStatus lists mirror statuses and needs owner rights.
94+ ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error)
95+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
96+ // Force. Manual requests without Force are at least 60 seconds apart.
97+ SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error)
98+}
99+
100+// NewMirrorServiceClient constructs a client for the ermite.MirrorService service. By default, it
101+// uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
102+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
103+// connect.WithGRPCWeb() options.
104+//
105+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
106+// http://api.acme.com or https://acme.com/grpc).
107+func NewMirrorServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MirrorServiceClient {
108+ baseURL = strings.TrimRight(baseURL, "/")
109+ mirrorServiceMethods := ermite.File_ermite_mirror_proto.Services().ByName("MirrorService").Methods()
110+ return &mirrorServiceClient{
111+ getMirrorKey: connect.NewClient[ermite.GetMirrorKeyRequest, ermite.GetMirrorKeyResponse](
112+ httpClient,
113+ baseURL+MirrorServiceGetMirrorKeyProcedure,
114+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorKey")),
115+ connect.WithClientOptions(opts...),
116+ ),
117+ rotateMirrorKey: connect.NewClient[ermite.RotateMirrorKeyRequest, ermite.RotateMirrorKeyResponse](
118+ httpClient,
119+ baseURL+MirrorServiceRotateMirrorKeyProcedure,
120+ connect.WithSchema(mirrorServiceMethods.ByName("RotateMirrorKey")),
121+ connect.WithClientOptions(opts...),
122+ ),
123+ getMirrorConfig: connect.NewClient[ermite.GetMirrorConfigRequest, ermite.GetMirrorConfigResponse](
124+ httpClient,
125+ baseURL+MirrorServiceGetMirrorConfigProcedure,
126+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorConfig")),
127+ connect.WithClientOptions(opts...),
128+ ),
129+ setMirrorConfig: connect.NewClient[ermite.SetMirrorConfigRequest, ermite.SetMirrorConfigResponse](
130+ httpClient,
131+ baseURL+MirrorServiceSetMirrorConfigProcedure,
132+ connect.WithSchema(mirrorServiceMethods.ByName("SetMirrorConfig")),
133+ connect.WithClientOptions(opts...),
134+ ),
135+ setRepoMirror: connect.NewClient[ermite.SetRepoMirrorRequest, ermite.SetRepoMirrorResponse](
136+ httpClient,
137+ baseURL+MirrorServiceSetRepoMirrorProcedure,
138+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirror")),
139+ connect.WithClientOptions(opts...),
140+ ),
141+ removeRepoMirror: connect.NewClient[ermite.RemoveRepoMirrorRequest, ermite.RemoveRepoMirrorResponse](
142+ httpClient,
143+ baseURL+MirrorServiceRemoveRepoMirrorProcedure,
144+ connect.WithSchema(mirrorServiceMethods.ByName("RemoveRepoMirror")),
145+ connect.WithClientOptions(opts...),
146+ ),
147+ setRepoMirrorExcluded: connect.NewClient[ermite.SetRepoMirrorExcludedRequest, ermite.SetRepoMirrorExcludedResponse](
148+ httpClient,
149+ baseURL+MirrorServiceSetRepoMirrorExcludedProcedure,
150+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirrorExcluded")),
151+ connect.WithClientOptions(opts...),
152+ ),
153+ getMirrorStatus: connect.NewClient[ermite.GetMirrorStatusRequest, ermite.GetMirrorStatusResponse](
154+ httpClient,
155+ baseURL+MirrorServiceGetMirrorStatusProcedure,
156+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorStatus")),
157+ connect.WithClientOptions(opts...),
158+ ),
159+ listMirrorStatus: connect.NewClient[ermite.ListMirrorStatusRequest, ermite.ListMirrorStatusResponse](
160+ httpClient,
161+ baseURL+MirrorServiceListMirrorStatusProcedure,
162+ connect.WithSchema(mirrorServiceMethods.ByName("ListMirrorStatus")),
163+ connect.WithClientOptions(opts...),
164+ ),
165+ syncMirror: connect.NewClient[ermite.SyncMirrorRequest, ermite.SyncMirrorResponse](
166+ httpClient,
167+ baseURL+MirrorServiceSyncMirrorProcedure,
168+ connect.WithSchema(mirrorServiceMethods.ByName("SyncMirror")),
169+ connect.WithClientOptions(opts...),
170+ ),
171+ }
172+}
173+
174+// mirrorServiceClient implements MirrorServiceClient.
175+type mirrorServiceClient struct {
176+ getMirrorKey *connect.Client[ermite.GetMirrorKeyRequest, ermite.GetMirrorKeyResponse]
177+ rotateMirrorKey *connect.Client[ermite.RotateMirrorKeyRequest, ermite.RotateMirrorKeyResponse]
178+ getMirrorConfig *connect.Client[ermite.GetMirrorConfigRequest, ermite.GetMirrorConfigResponse]
179+ setMirrorConfig *connect.Client[ermite.SetMirrorConfigRequest, ermite.SetMirrorConfigResponse]
180+ setRepoMirror *connect.Client[ermite.SetRepoMirrorRequest, ermite.SetRepoMirrorResponse]
181+ removeRepoMirror *connect.Client[ermite.RemoveRepoMirrorRequest, ermite.RemoveRepoMirrorResponse]
182+ setRepoMirrorExcluded *connect.Client[ermite.SetRepoMirrorExcludedRequest, ermite.SetRepoMirrorExcludedResponse]
183+ getMirrorStatus *connect.Client[ermite.GetMirrorStatusRequest, ermite.GetMirrorStatusResponse]
184+ listMirrorStatus *connect.Client[ermite.ListMirrorStatusRequest, ermite.ListMirrorStatusResponse]
185+ syncMirror *connect.Client[ermite.SyncMirrorRequest, ermite.SyncMirrorResponse]
186+}
187+
188+// GetMirrorKey calls ermite.MirrorService.GetMirrorKey.
189+func (c *mirrorServiceClient) GetMirrorKey(ctx context.Context, req *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error) {
190+ return c.getMirrorKey.CallUnary(ctx, req)
191+}
192+
193+// RotateMirrorKey calls ermite.MirrorService.RotateMirrorKey.
194+func (c *mirrorServiceClient) RotateMirrorKey(ctx context.Context, req *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error) {
195+ return c.rotateMirrorKey.CallUnary(ctx, req)
196+}
197+
198+// GetMirrorConfig calls ermite.MirrorService.GetMirrorConfig.
199+func (c *mirrorServiceClient) GetMirrorConfig(ctx context.Context, req *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error) {
200+ return c.getMirrorConfig.CallUnary(ctx, req)
201+}
202+
203+// SetMirrorConfig calls ermite.MirrorService.SetMirrorConfig.
204+func (c *mirrorServiceClient) SetMirrorConfig(ctx context.Context, req *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error) {
205+ return c.setMirrorConfig.CallUnary(ctx, req)
206+}
207+
208+// SetRepoMirror calls ermite.MirrorService.SetRepoMirror.
209+func (c *mirrorServiceClient) SetRepoMirror(ctx context.Context, req *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error) {
210+ return c.setRepoMirror.CallUnary(ctx, req)
211+}
212+
213+// RemoveRepoMirror calls ermite.MirrorService.RemoveRepoMirror.
214+func (c *mirrorServiceClient) RemoveRepoMirror(ctx context.Context, req *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error) {
215+ return c.removeRepoMirror.CallUnary(ctx, req)
216+}
217+
218+// SetRepoMirrorExcluded calls ermite.MirrorService.SetRepoMirrorExcluded.
219+func (c *mirrorServiceClient) SetRepoMirrorExcluded(ctx context.Context, req *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error) {
220+ return c.setRepoMirrorExcluded.CallUnary(ctx, req)
221+}
222+
223+// GetMirrorStatus calls ermite.MirrorService.GetMirrorStatus.
224+func (c *mirrorServiceClient) GetMirrorStatus(ctx context.Context, req *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error) {
225+ return c.getMirrorStatus.CallUnary(ctx, req)
226+}
227+
228+// ListMirrorStatus calls ermite.MirrorService.ListMirrorStatus.
229+func (c *mirrorServiceClient) ListMirrorStatus(ctx context.Context, req *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error) {
230+ return c.listMirrorStatus.CallUnary(ctx, req)
231+}
232+
233+// SyncMirror calls ermite.MirrorService.SyncMirror.
234+func (c *mirrorServiceClient) SyncMirror(ctx context.Context, req *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error) {
235+ return c.syncMirror.CallUnary(ctx, req)
236+}
237+
238+// MirrorServiceHandler is an implementation of the ermite.MirrorService service.
239+type MirrorServiceHandler interface {
240+ // GetMirrorKey returns the public mirror key, generating it on first use.
241+ // Needs a non-anonymous caller. Fails with failed_precondition without
242+ // ERMITE_SECRETS_KEY.
243+ GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error)
244+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
245+ // and sync with force to recover from authentication failures.
246+ RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error)
247+ // GetMirrorConfig returns the global mirror policy.
248+ GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error)
249+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
250+ // Invalid fields give invalid_argument with field violations.
251+ SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error)
252+ // SetRepoMirror sets an override, even when global mirroring is disabled.
253+ SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error)
254+ // RemoveRepoMirror removes the override so the global policy applies again.
255+ RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error)
256+ // SetRepoMirrorExcluded excludes or includes a repository in the global
257+ // policy. An explicit URL override takes precedence.
258+ SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error)
259+ // GetMirrorStatus returns the resolved target and status of one repository.
260+ GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error)
261+ // ListMirrorStatus lists mirror statuses and needs owner rights.
262+ ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error)
263+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
264+ // Force. Manual requests without Force are at least 60 seconds apart.
265+ SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error)
266+}
267+
268+// NewMirrorServiceHandler builds an HTTP handler from the service implementation. It returns the
269+// path on which to mount the handler and the handler itself.
270+//
271+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
272+// and JSON codecs. They also support gzip compression.
273+func NewMirrorServiceHandler(svc MirrorServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
274+ mirrorServiceMethods := ermite.File_ermite_mirror_proto.Services().ByName("MirrorService").Methods()
275+ mirrorServiceGetMirrorKeyHandler := connect.NewUnaryHandler(
276+ MirrorServiceGetMirrorKeyProcedure,
277+ svc.GetMirrorKey,
278+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorKey")),
279+ connect.WithHandlerOptions(opts...),
280+ )
281+ mirrorServiceRotateMirrorKeyHandler := connect.NewUnaryHandler(
282+ MirrorServiceRotateMirrorKeyProcedure,
283+ svc.RotateMirrorKey,
284+ connect.WithSchema(mirrorServiceMethods.ByName("RotateMirrorKey")),
285+ connect.WithHandlerOptions(opts...),
286+ )
287+ mirrorServiceGetMirrorConfigHandler := connect.NewUnaryHandler(
288+ MirrorServiceGetMirrorConfigProcedure,
289+ svc.GetMirrorConfig,
290+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorConfig")),
291+ connect.WithHandlerOptions(opts...),
292+ )
293+ mirrorServiceSetMirrorConfigHandler := connect.NewUnaryHandler(
294+ MirrorServiceSetMirrorConfigProcedure,
295+ svc.SetMirrorConfig,
296+ connect.WithSchema(mirrorServiceMethods.ByName("SetMirrorConfig")),
297+ connect.WithHandlerOptions(opts...),
298+ )
299+ mirrorServiceSetRepoMirrorHandler := connect.NewUnaryHandler(
300+ MirrorServiceSetRepoMirrorProcedure,
301+ svc.SetRepoMirror,
302+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirror")),
303+ connect.WithHandlerOptions(opts...),
304+ )
305+ mirrorServiceRemoveRepoMirrorHandler := connect.NewUnaryHandler(
306+ MirrorServiceRemoveRepoMirrorProcedure,
307+ svc.RemoveRepoMirror,
308+ connect.WithSchema(mirrorServiceMethods.ByName("RemoveRepoMirror")),
309+ connect.WithHandlerOptions(opts...),
310+ )
311+ mirrorServiceSetRepoMirrorExcludedHandler := connect.NewUnaryHandler(
312+ MirrorServiceSetRepoMirrorExcludedProcedure,
313+ svc.SetRepoMirrorExcluded,
314+ connect.WithSchema(mirrorServiceMethods.ByName("SetRepoMirrorExcluded")),
315+ connect.WithHandlerOptions(opts...),
316+ )
317+ mirrorServiceGetMirrorStatusHandler := connect.NewUnaryHandler(
318+ MirrorServiceGetMirrorStatusProcedure,
319+ svc.GetMirrorStatus,
320+ connect.WithSchema(mirrorServiceMethods.ByName("GetMirrorStatus")),
321+ connect.WithHandlerOptions(opts...),
322+ )
323+ mirrorServiceListMirrorStatusHandler := connect.NewUnaryHandler(
324+ MirrorServiceListMirrorStatusProcedure,
325+ svc.ListMirrorStatus,
326+ connect.WithSchema(mirrorServiceMethods.ByName("ListMirrorStatus")),
327+ connect.WithHandlerOptions(opts...),
328+ )
329+ mirrorServiceSyncMirrorHandler := connect.NewUnaryHandler(
330+ MirrorServiceSyncMirrorProcedure,
331+ svc.SyncMirror,
332+ connect.WithSchema(mirrorServiceMethods.ByName("SyncMirror")),
333+ connect.WithHandlerOptions(opts...),
334+ )
335+ return "/ermite.MirrorService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
336+ switch r.URL.Path {
337+ case MirrorServiceGetMirrorKeyProcedure:
338+ mirrorServiceGetMirrorKeyHandler.ServeHTTP(w, r)
339+ case MirrorServiceRotateMirrorKeyProcedure:
340+ mirrorServiceRotateMirrorKeyHandler.ServeHTTP(w, r)
341+ case MirrorServiceGetMirrorConfigProcedure:
342+ mirrorServiceGetMirrorConfigHandler.ServeHTTP(w, r)
343+ case MirrorServiceSetMirrorConfigProcedure:
344+ mirrorServiceSetMirrorConfigHandler.ServeHTTP(w, r)
345+ case MirrorServiceSetRepoMirrorProcedure:
346+ mirrorServiceSetRepoMirrorHandler.ServeHTTP(w, r)
347+ case MirrorServiceRemoveRepoMirrorProcedure:
348+ mirrorServiceRemoveRepoMirrorHandler.ServeHTTP(w, r)
349+ case MirrorServiceSetRepoMirrorExcludedProcedure:
350+ mirrorServiceSetRepoMirrorExcludedHandler.ServeHTTP(w, r)
351+ case MirrorServiceGetMirrorStatusProcedure:
352+ mirrorServiceGetMirrorStatusHandler.ServeHTTP(w, r)
353+ case MirrorServiceListMirrorStatusProcedure:
354+ mirrorServiceListMirrorStatusHandler.ServeHTTP(w, r)
355+ case MirrorServiceSyncMirrorProcedure:
356+ mirrorServiceSyncMirrorHandler.ServeHTTP(w, r)
357+ default:
358+ http.NotFound(w, r)
359+ }
360+ })
361+}
362+
363+// UnimplementedMirrorServiceHandler returns CodeUnimplemented from all methods.
364+type UnimplementedMirrorServiceHandler struct{}
365+
366+func (UnimplementedMirrorServiceHandler) GetMirrorKey(context.Context, *connect.Request[ermite.GetMirrorKeyRequest]) (*connect.Response[ermite.GetMirrorKeyResponse], error) {
367+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorKey is not implemented"))
368+}
369+
370+func (UnimplementedMirrorServiceHandler) RotateMirrorKey(context.Context, *connect.Request[ermite.RotateMirrorKeyRequest]) (*connect.Response[ermite.RotateMirrorKeyResponse], error) {
371+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.RotateMirrorKey is not implemented"))
372+}
373+
374+func (UnimplementedMirrorServiceHandler) GetMirrorConfig(context.Context, *connect.Request[ermite.GetMirrorConfigRequest]) (*connect.Response[ermite.GetMirrorConfigResponse], error) {
375+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorConfig is not implemented"))
376+}
377+
378+func (UnimplementedMirrorServiceHandler) SetMirrorConfig(context.Context, *connect.Request[ermite.SetMirrorConfigRequest]) (*connect.Response[ermite.SetMirrorConfigResponse], error) {
379+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetMirrorConfig is not implemented"))
380+}
381+
382+func (UnimplementedMirrorServiceHandler) SetRepoMirror(context.Context, *connect.Request[ermite.SetRepoMirrorRequest]) (*connect.Response[ermite.SetRepoMirrorResponse], error) {
383+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetRepoMirror is not implemented"))
384+}
385+
386+func (UnimplementedMirrorServiceHandler) RemoveRepoMirror(context.Context, *connect.Request[ermite.RemoveRepoMirrorRequest]) (*connect.Response[ermite.RemoveRepoMirrorResponse], error) {
387+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.RemoveRepoMirror is not implemented"))
388+}
389+
390+func (UnimplementedMirrorServiceHandler) SetRepoMirrorExcluded(context.Context, *connect.Request[ermite.SetRepoMirrorExcludedRequest]) (*connect.Response[ermite.SetRepoMirrorExcludedResponse], error) {
391+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SetRepoMirrorExcluded is not implemented"))
392+}
393+
394+func (UnimplementedMirrorServiceHandler) GetMirrorStatus(context.Context, *connect.Request[ermite.GetMirrorStatusRequest]) (*connect.Response[ermite.GetMirrorStatusResponse], error) {
395+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.GetMirrorStatus is not implemented"))
396+}
397+
398+func (UnimplementedMirrorServiceHandler) ListMirrorStatus(context.Context, *connect.Request[ermite.ListMirrorStatusRequest]) (*connect.Response[ermite.ListMirrorStatusResponse], error) {
399+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.ListMirrorStatus is not implemented"))
400+}
401+
402+func (UnimplementedMirrorServiceHandler) SyncMirror(context.Context, *connect.Request[ermite.SyncMirrorRequest]) (*connect.Response[ermite.SyncMirrorResponse], error) {
403+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.MirrorService.SyncMirror is not implemented"))
404+}
M gen/ermite/event.pb.go +215 -41 generated file: collapsed
A gen/ermite/mirror.pb.go +1814 -0 generated file: collapsed
M gen_test.go +45 -0
@@ -19,6 +19,17 @@ import (
1919 // TestProcedures pins the procedure paths: they are the wire contract.
2020 func TestProcedures(t *testing.T) {
2121 tests := []struct{ got, want string }{
22+ {ermiteconnect.MirrorServiceGetMirrorKeyProcedure, "/ermite.MirrorService/GetMirrorKey"},
23+ {ermiteconnect.MirrorServiceRotateMirrorKeyProcedure, "/ermite.MirrorService/RotateMirrorKey"},
24+ {ermiteconnect.MirrorServiceGetMirrorConfigProcedure, "/ermite.MirrorService/GetMirrorConfig"},
25+ {ermiteconnect.MirrorServiceSetMirrorConfigProcedure, "/ermite.MirrorService/SetMirrorConfig"},
26+ {ermiteconnect.MirrorServiceSetRepoMirrorProcedure, "/ermite.MirrorService/SetRepoMirror"},
27+ {ermiteconnect.MirrorServiceRemoveRepoMirrorProcedure, "/ermite.MirrorService/RemoveRepoMirror"},
28+ {ermiteconnect.MirrorServiceSetRepoMirrorExcludedProcedure, "/ermite.MirrorService/SetRepoMirrorExcluded"},
29+ {ermiteconnect.MirrorServiceGetMirrorStatusProcedure, "/ermite.MirrorService/GetMirrorStatus"},
30+ {ermiteconnect.MirrorServiceListMirrorStatusProcedure, "/ermite.MirrorService/ListMirrorStatus"},
31+ {ermiteconnect.MirrorServiceSyncMirrorProcedure, "/ermite.MirrorService/SyncMirror"},
32+
2233 {ermiteconnect.RepoServiceListReposProcedure, "/ermite.RepoService/ListRepos"},
2334 {ermiteconnect.RepoServiceGetRepoProcedure, "/ermite.RepoService/GetRepo"},
2435 {ermiteconnect.RepoServiceCreateRepoProcedure, "/ermite.RepoService/CreateRepo"},
@@ -569,3 +580,37 @@ func TestCI3RoundTrip(t *testing.T) {
569580 }
570581 }
571582 }
583+
584+// TestMirrorWire pins event numbers and verifies presence of explicit zero patches.
585+func TestMirrorWire(t *testing.T) {
586+ fields := (&ermite.Event{}).ProtoReflect().Descriptor().Fields()
587+ for name, number := range map[protoreflect.Name]protoreflect.FieldNumber{"mirror_failed": 23, "mirror_recovered": 24} {
588+ if fd := fields.ByName(name); fd == nil || fd.Number() != number || fd.ContainingOneof().Name() != "kind" {
589+ t.Fatalf("Event.%s: %v, want kind field %d", name, fd, number)
590+ }
591+ }
592+ for _, msg := range []proto.Message{&ermite.MirrorFailed{}, &ermite.MirrorRecovered{}} {
593+ fields := msg.ProtoReflect().Descriptor().Fields()
594+ if fields.ByName("url") != nil || fields.ByName("detail") != nil {
595+ t.Fatalf("%T leaks URL or detail", msg)
596+ }
597+ }
598+ for _, in := range []proto.Message{
599+ &ermite.SetMirrorConfigRequest{Enabled: proto.Bool(false), UrlTemplate: proto.String(""), Cron: proto.String(""), AllBranches: proto.Bool(false)},
600+ &ermite.SetRepoMirrorRequest{Repo: "repo", AllBranches: proto.Bool(false)},
601+ &ermite.Event{Kind: &ermite.Event_MirrorFailed{MirrorFailed: &ermite.MirrorFailed{Repo: "repo", State: ermite.MirrorState_MIRROR_STATE_PAUSED, FailureClass: "diverged", Reason: "remote diverged"}}},
602+ &ermite.Event{Kind: &ermite.Event_MirrorRecovered{MirrorRecovered: &ermite.MirrorRecovered{Repo: "repo"}}},
603+ } {
604+ b, err := proto.Marshal(in)
605+ if err != nil {
606+ t.Fatal(err)
607+ }
608+ out := in.ProtoReflect().New().Interface()
609+ if err := proto.Unmarshal(b, out); err != nil {
610+ t.Fatal(err)
611+ }
612+ if !proto.Equal(in, out) {
613+ t.Errorf("%T: round trip mismatch: %v != %v", in, out, in)
614+ }
615+ }
616+}
@@ -19,6 +19,17 @@ import (
19 // TestProcedures pins the procedure paths: they are the wire contract.19 // TestProcedures pins the procedure paths: they are the wire contract.
20 func TestProcedures(t *testing.T) {20 func TestProcedures(t *testing.T) {
21 tests := []struct{ got, want string }{21 tests := []struct{ got, want string }{
22+ {ermiteconnect.MirrorServiceGetMirrorKeyProcedure, "/ermite.MirrorService/GetMirrorKey"},
23+ {ermiteconnect.MirrorServiceRotateMirrorKeyProcedure, "/ermite.MirrorService/RotateMirrorKey"},
24+ {ermiteconnect.MirrorServiceGetMirrorConfigProcedure, "/ermite.MirrorService/GetMirrorConfig"},
25+ {ermiteconnect.MirrorServiceSetMirrorConfigProcedure, "/ermite.MirrorService/SetMirrorConfig"},
26+ {ermiteconnect.MirrorServiceSetRepoMirrorProcedure, "/ermite.MirrorService/SetRepoMirror"},
27+ {ermiteconnect.MirrorServiceRemoveRepoMirrorProcedure, "/ermite.MirrorService/RemoveRepoMirror"},
28+ {ermiteconnect.MirrorServiceSetRepoMirrorExcludedProcedure, "/ermite.MirrorService/SetRepoMirrorExcluded"},
29+ {ermiteconnect.MirrorServiceGetMirrorStatusProcedure, "/ermite.MirrorService/GetMirrorStatus"},
30+ {ermiteconnect.MirrorServiceListMirrorStatusProcedure, "/ermite.MirrorService/ListMirrorStatus"},
31+ {ermiteconnect.MirrorServiceSyncMirrorProcedure, "/ermite.MirrorService/SyncMirror"},
32+
22 {ermiteconnect.RepoServiceListReposProcedure, "/ermite.RepoService/ListRepos"},33 {ermiteconnect.RepoServiceListReposProcedure, "/ermite.RepoService/ListRepos"},
23 {ermiteconnect.RepoServiceGetRepoProcedure, "/ermite.RepoService/GetRepo"},34 {ermiteconnect.RepoServiceGetRepoProcedure, "/ermite.RepoService/GetRepo"},
24 {ermiteconnect.RepoServiceCreateRepoProcedure, "/ermite.RepoService/CreateRepo"},35 {ermiteconnect.RepoServiceCreateRepoProcedure, "/ermite.RepoService/CreateRepo"},
@@ -569,3 +580,37 @@ func TestCI3RoundTrip(t *testing.T) {
569 }580 }
570 }581 }
571 }582 }
583+
584+// TestMirrorWire pins event numbers and verifies presence of explicit zero patches.
585+func TestMirrorWire(t *testing.T) {
586+ fields := (&ermite.Event{}).ProtoReflect().Descriptor().Fields()
587+ for name, number := range map[protoreflect.Name]protoreflect.FieldNumber{"mirror_failed": 23, "mirror_recovered": 24} {
588+ if fd := fields.ByName(name); fd == nil || fd.Number() != number || fd.ContainingOneof().Name() != "kind" {
589+ t.Fatalf("Event.%s: %v, want kind field %d", name, fd, number)
590+ }
591+ }
592+ for _, msg := range []proto.Message{&ermite.MirrorFailed{}, &ermite.MirrorRecovered{}} {
593+ fields := msg.ProtoReflect().Descriptor().Fields()
594+ if fields.ByName("url") != nil || fields.ByName("detail") != nil {
595+ t.Fatalf("%T leaks URL or detail", msg)
596+ }
597+ }
598+ for _, in := range []proto.Message{
599+ &ermite.SetMirrorConfigRequest{Enabled: proto.Bool(false), UrlTemplate: proto.String(""), Cron: proto.String(""), AllBranches: proto.Bool(false)},
600+ &ermite.SetRepoMirrorRequest{Repo: "repo", AllBranches: proto.Bool(false)},
601+ &ermite.Event{Kind: &ermite.Event_MirrorFailed{MirrorFailed: &ermite.MirrorFailed{Repo: "repo", State: ermite.MirrorState_MIRROR_STATE_PAUSED, FailureClass: "diverged", Reason: "remote diverged"}}},
602+ &ermite.Event{Kind: &ermite.Event_MirrorRecovered{MirrorRecovered: &ermite.MirrorRecovered{Repo: "repo"}}},
603+ } {
604+ b, err := proto.Marshal(in)
605+ if err != nil {
606+ t.Fatal(err)
607+ }
608+ out := in.ProtoReflect().New().Interface()
609+ if err := proto.Unmarshal(b, out); err != nil {
610+ t.Fatal(err)
611+ }
612+ if !proto.Equal(in, out) {
613+ t.Errorf("%T: round trip mismatch: %v != %v", in, out, in)
614+ }
615+ }
616+}
M proto/ermite/event.proto +27 -0
@@ -6,6 +6,7 @@ package ermite;
66
77 import "ermite/issue.proto";
88 import "ermite/merge_request.proto";
9+import "ermite/mirror.proto";
910 import "ermite/repo.proto";
1011 import "ermite/run.proto";
1112 import "google/protobuf/timestamp.proto";
@@ -135,6 +136,10 @@ message Event {
135136 // MergeRequestMergeability: the mergeability of an open merge request
136137 // changed.
137138 MergeRequestMergeability merge_request_mergeability = 22;
139+ // MirrorFailed: a mirror entered failing or paused.
140+ MirrorFailed mirror_failed = 23;
141+ // MirrorRecovered: a failing or paused mirror succeeded again.
142+ MirrorRecovered mirror_recovered = 24;
138143 }
139144 }
140145
@@ -306,3 +311,25 @@ message JobUpdated {
306311 // Job is the job after the change, with its steps.
307312 RunJob job = 1;
308313 }
314+
315+// MirrorFailed reports a transition into failing or paused, including failing
316+// to paused, not every failed attempt. Servers with feature "mirror" deliver it
317+// on "repo:<name>" and "home", subject to repository visibility. It carries no
318+// URL or error detail.
319+message MirrorFailed {
320+ // Repo is the repository name.
321+ string repo = 1;
322+ // State is the new mirror state.
323+ MirrorState state = 2;
324+ // FailureClass is the failure category.
325+ string failure_class = 3;
326+ // Reason explains the failure without a URL or error detail.
327+ string reason = 4;
328+}
329+
330+// MirrorRecovered reports a successful sync after failing or paused. Delivery
331+// follows MirrorFailed. It carries no URL or error detail.
332+message MirrorRecovered {
333+ // Repo is the repository name.
334+ string repo = 1;
335+}
@@ -6,6 +6,7 @@ package ermite;
6 6
7 import "ermite/issue.proto";7 import "ermite/issue.proto";
8 import "ermite/merge_request.proto";8 import "ermite/merge_request.proto";
9+import "ermite/mirror.proto";
9 import "ermite/repo.proto";10 import "ermite/repo.proto";
10 import "ermite/run.proto";11 import "ermite/run.proto";
11 import "google/protobuf/timestamp.proto";12 import "google/protobuf/timestamp.proto";
@@ -135,6 +136,10 @@ message Event {
135 // MergeRequestMergeability: the mergeability of an open merge request136 // MergeRequestMergeability: the mergeability of an open merge request
136 // changed.137 // changed.
137 MergeRequestMergeability merge_request_mergeability = 22;138 MergeRequestMergeability merge_request_mergeability = 22;
139+ // MirrorFailed: a mirror entered failing or paused.
140+ MirrorFailed mirror_failed = 23;
141+ // MirrorRecovered: a failing or paused mirror succeeded again.
142+ MirrorRecovered mirror_recovered = 24;
138 }143 }
139 }144 }
140 145
@@ -306,3 +311,25 @@ message JobUpdated {
306 // Job is the job after the change, with its steps.311 // Job is the job after the change, with its steps.
307 RunJob job = 1;312 RunJob job = 1;
308 }313 }
314+
315+// MirrorFailed reports a transition into failing or paused, including failing
316+// to paused, not every failed attempt. Servers with feature "mirror" deliver it
317+// on "repo:<name>" and "home", subject to repository visibility. It carries no
318+// URL or error detail.
319+message MirrorFailed {
320+ // Repo is the repository name.
321+ string repo = 1;
322+ // State is the new mirror state.
323+ MirrorState state = 2;
324+ // FailureClass is the failure category.
325+ string failure_class = 3;
326+ // Reason explains the failure without a URL or error detail.
327+ string reason = 4;
328+}
329+
330+// MirrorRecovered reports a successful sync after failing or paused. Delivery
331+// follows MirrorFailed. It carries no URL or error detail.
332+message MirrorRecovered {
333+ // Repo is the repository name.
334+ string repo = 1;
335+}
A proto/ermite/mirror.proto +296 -0
@@ -0,0 +1,296 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// MirrorService configures the push mirrors of the server. Global settings
10+// and key
11+// rotation need the admin token or a token with every scope and no repository
12+// or
13+// branch limits; per-repository settings and SyncMirror need repo:admin on
14+// the
15+// repository; status needs read access. GetMirrorKey needs a non-anonymous
16+// caller.
17+// ListMirrorStatus and SyncMirror with All need owner rights. No new token
18+// scope
19+// is required. Servers implementing this service list "mirror" in
20+// GetVersionResponse.features.
21+service MirrorService {
22+ // GetMirrorKey returns the public mirror key, generating it on first use.
23+ // Needs a non-anonymous caller. Fails with failed_precondition without
24+ // ERMITE_SECRETS_KEY.
25+ rpc GetMirrorKey(GetMirrorKeyRequest) returns (GetMirrorKeyResponse);
26+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
27+ // and sync with force to recover from authentication failures.
28+ rpc RotateMirrorKey(RotateMirrorKeyRequest) returns (RotateMirrorKeyResponse);
29+ // GetMirrorConfig returns the global mirror policy.
30+ rpc GetMirrorConfig(GetMirrorConfigRequest) returns (GetMirrorConfigResponse);
31+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
32+ // Invalid fields give invalid_argument with field violations.
33+ rpc SetMirrorConfig(SetMirrorConfigRequest) returns (SetMirrorConfigResponse);
34+ // SetRepoMirror sets an override, even when global mirroring is disabled.
35+ rpc SetRepoMirror(SetRepoMirrorRequest) returns (SetRepoMirrorResponse);
36+ // RemoveRepoMirror removes the override so the global policy applies again.
37+ rpc RemoveRepoMirror(RemoveRepoMirrorRequest) returns (RemoveRepoMirrorResponse);
38+ // SetRepoMirrorExcluded excludes or includes a repository in the global
39+ // policy. An explicit URL override takes precedence.
40+ rpc SetRepoMirrorExcluded(SetRepoMirrorExcludedRequest) returns (SetRepoMirrorExcludedResponse);
41+ // GetMirrorStatus returns the resolved target and status of one repository.
42+ rpc GetMirrorStatus(GetMirrorStatusRequest) returns (GetMirrorStatusResponse);
43+ // ListMirrorStatus lists mirror statuses and needs owner rights.
44+ rpc ListMirrorStatus(ListMirrorStatusRequest) returns (ListMirrorStatusResponse);
45+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
46+ // Force. Manual requests without Force are at least 60 seconds apart.
47+ rpc SyncMirror(SyncMirrorRequest) returns (SyncMirrorResponse);
48+}
49+
50+// MirrorKey is the public SSH key used for push mirrors. Private key material
51+// is never returned.
52+message MirrorKey {
53+ // AuthorizedKey is the SSH authorized_keys line.
54+ string authorized_key = 1;
55+ // Fingerprint is the SHA256 fingerprint of the public key.
56+ string fingerprint = 2;
57+ // CreatedAt is when the key was created.
58+ google.protobuf.Timestamp created_at = 3;
59+}
60+
61+// GetMirrorKeyRequest selects the input of GetMirrorKey.
62+message GetMirrorKeyRequest {}
63+
64+// GetMirrorKeyResponse holds the result of GetMirrorKey.
65+message GetMirrorKeyResponse {
66+ // Key is the public mirror key.
67+ MirrorKey key = 1;
68+}
69+
70+// RotateMirrorKeyRequest selects the input of RotateMirrorKey.
71+message RotateMirrorKeyRequest {}
72+
73+// RotateMirrorKeyResponse holds the result of RotateMirrorKey.
74+message RotateMirrorKeyResponse {
75+ // Key is the public mirror key.
76+ MirrorKey key = 1;
77+ // PreviousFingerprint is the fingerprint of the replaced key.
78+ string previous_fingerprint = 2;
79+}
80+
81+// MirrorConfig is the server-wide push mirror policy.
82+message MirrorConfig {
83+ // Enabled says whether mirroring is enabled.
84+ bool enabled = 1;
85+ // URLTemplate is an SSH URL containing "{repo}", replaced with the
86+ // repository name.
87+ string url_template = 2;
88+ // Cron is a five-field cron expression in UTC, no more frequent than every
89+ // five minutes.
90+ string cron = 3;
91+ // AllBranches includes every branch rather than only the default branch.
92+ // Tags are always included.
93+ bool all_branches = 4;
94+ // UpdatedAt is when the configuration was last changed.
95+ google.protobuf.Timestamp updated_at = 5;
96+}
97+
98+// GetMirrorConfigRequest selects the input of GetMirrorConfig.
99+message GetMirrorConfigRequest {}
100+
101+// GetMirrorConfigResponse holds the result of GetMirrorConfig.
102+message GetMirrorConfigResponse {
103+ // Config is the global mirror policy.
104+ MirrorConfig config = 1;
105+}
106+
107+// SetMirrorConfigRequest selects the input of SetMirrorConfig.
108+message SetMirrorConfigRequest {
109+ // Enabled says whether mirroring is enabled. Unset means unchanged.
110+ optional bool enabled = 1;
111+ // URLTemplate is an SSH URL containing "{repo}", replaced with the
112+ // repository name. Unset means unchanged.
113+ optional string url_template = 2;
114+ // Cron is a five-field cron expression in UTC, no more frequent than every
115+ // five minutes. Unset means unchanged.
116+ optional string cron = 3;
117+ // AllBranches includes every branch rather than only the default branch.
118+ // Tags are always included. Unset means unchanged.
119+ optional bool all_branches = 4;
120+}
121+
122+// SetMirrorConfigResponse holds the result of SetMirrorConfig.
123+message SetMirrorConfigResponse {
124+ // Config is the global mirror policy.
125+ MirrorConfig config = 1;
126+}
127+
128+// MirrorSource says which configuration applies to a repository.
129+enum MirrorSource {
130+ // Unspecified.
131+ MIRROR_SOURCE_UNSPECIFIED = 0;
132+ // Disabled.
133+ MIRROR_SOURCE_DISABLED = 1;
134+ // Global.
135+ MIRROR_SOURCE_GLOBAL = 2;
136+ // Override.
137+ MIRROR_SOURCE_OVERRIDE = 3;
138+ // Excluded.
139+ MIRROR_SOURCE_EXCLUDED = 4;
140+}
141+
142+// MirrorTarget is the resolved push mirror target of a repository.
143+message MirrorTarget {
144+ // Repo is the repository name.
145+ string repo = 1;
146+ // Enabled says whether mirroring is enabled.
147+ bool enabled = 2;
148+ // URL is the SSH destination of the mirror.
149+ string url = 3;
150+ // Cron is a five-field cron expression in UTC, no more frequent than every
151+ // five minutes.
152+ string cron = 4;
153+ // AllBranches includes every branch rather than only the default branch.
154+ // Tags are always included.
155+ bool all_branches = 5;
156+ // Source is the configuration used to resolve the target.
157+ MirrorSource source = 6;
158+}
159+
160+// SetRepoMirrorRequest selects the input of SetRepoMirror.
161+message SetRepoMirrorRequest {
162+ // Repo is the repository name.
163+ string repo = 1;
164+ // URL is the SSH destination of the mirror.
165+ string url = 2;
166+ // Cron is a five-field cron expression in UTC, no more frequent than every
167+ // five minutes.
168+ // Empty means inherit the global cron.
169+ string cron = 3;
170+ // AllBranches includes every branch rather than only the default branch.
171+ // Tags are always included. Unset means inherit the global setting.
172+ optional bool all_branches = 4;
173+}
174+
175+// SetRepoMirrorResponse holds the result of SetRepoMirror.
176+message SetRepoMirrorResponse {
177+ // Target is the resolved mirror target.
178+ MirrorTarget target = 1;
179+}
180+
181+// RemoveRepoMirrorRequest selects the input of RemoveRepoMirror.
182+message RemoveRepoMirrorRequest {
183+ // Repo is the repository name.
184+ string repo = 1;
185+}
186+
187+// RemoveRepoMirrorResponse holds the result of RemoveRepoMirror.
188+message RemoveRepoMirrorResponse {}
189+
190+// SetRepoMirrorExcludedRequest selects the input of SetRepoMirrorExcluded.
191+message SetRepoMirrorExcludedRequest {
192+ // Repo is the repository name.
193+ string repo = 1;
194+ // Excluded opts out of the global template, unless a URL override exists.
195+ bool excluded = 2;
196+}
197+
198+// SetRepoMirrorExcludedResponse holds the result of SetRepoMirrorExcluded.
199+message SetRepoMirrorExcludedResponse {
200+ // Target is the resolved mirror target.
201+ MirrorTarget target = 1;
202+}
203+
204+// MirrorState is the current state of a push mirror.
205+enum MirrorState {
206+ // Unspecified.
207+ MIRROR_STATE_UNSPECIFIED = 0;
208+ // Disabled.
209+ MIRROR_STATE_DISABLED = 1;
210+ // Ok.
211+ MIRROR_STATE_OK = 2;
212+ // Failing.
213+ MIRROR_STATE_FAILING = 3;
214+ // Paused.
215+ MIRROR_STATE_PAUSED = 4;
216+}
217+
218+// MirrorStatus describes a repository mirror and its last attempts.
219+message MirrorStatus {
220+ // Target is the resolved mirror target.
221+ MirrorTarget target = 1;
222+ // State is the current mirror state.
223+ MirrorState state = 2;
224+ // FailureClass is the failure category, empty when there is no failure.
225+ string failure_class = 3;
226+ // Reason explains the current state.
227+ string reason = 4;
228+ // Detail is the last error detail, returned only to callers with repo:admin
229+ // on the repository.
230+ string detail = 5;
231+ // ConsecutiveFailures counts failed attempts since the last success or
232+ // reset.
233+ int32 consecutive_failures = 6;
234+ // LastAttemptAt is when the last sync was attempted.
235+ google.protobuf.Timestamp last_attempt_at = 7;
236+ // LastSuccessAt is when the last sync succeeded, including a no-op.
237+ google.protobuf.Timestamp last_success_at = 8;
238+ // NextRetryAt is the next retry time while failing; otherwise unset.
239+ google.protobuf.Timestamp next_retry_at = 9;
240+ // NextRunAt is the next scheduled sync time, or the retry time while
241+ // failing.
242+ google.protobuf.Timestamp next_run_at = 10;
243+ // PausedAt is when the mirror was paused; otherwise unset.
244+ google.protobuf.Timestamp paused_at = 11;
245+}
246+
247+// GetMirrorStatusRequest selects the input of GetMirrorStatus.
248+message GetMirrorStatusRequest {
249+ // Repo is the repository name.
250+ string repo = 1;
251+}
252+
253+// GetMirrorStatusResponse holds the result of GetMirrorStatus.
254+message GetMirrorStatusResponse {
255+ // Status is the current mirror status.
256+ MirrorStatus status = 1;
257+}
258+
259+// ListMirrorStatusRequest selects the input of ListMirrorStatus.
260+message ListMirrorStatusRequest {}
261+
262+// ListMirrorStatusResponse holds the result of ListMirrorStatus.
263+message ListMirrorStatusResponse {
264+ // Statuses are the mirror statuses.
265+ repeated MirrorStatus statuses = 1;
266+}
267+
268+// SyncMirrorRequest selects the input of SyncMirror.
269+message SyncMirrorRequest {
270+ // Repo is the repository name.
271+ string repo = 1;
272+ // All selects every repository and needs owner rights. Repo must be empty
273+ // when true.
274+ bool all = 2;
275+ // Force lifts a pause and skips the no-op check and the 60-second manual
276+ // throttle. It never forces a Git push.
277+ bool force = 3;
278+}
279+
280+// SyncMirrorResult is the result of a sync request for one repository.
281+message SyncMirrorResult {
282+ // Repo is the repository name.
283+ string repo = 1;
284+ // Queued says another sync is running and this request was queued.
285+ bool queued = 2;
286+ // NoOp says the refs already matched the last successful snapshot.
287+ bool no_op = 3;
288+ // Status is the current mirror status.
289+ MirrorStatus status = 4;
290+}
291+
292+// SyncMirrorResponse holds the result of SyncMirror.
293+message SyncMirrorResponse {
294+ // Results are the per-repository sync results.
295+ repeated SyncMirrorResult results = 1;
296+}
@@ -0,0 +1,296 @@
1+// SPDX-License-Identifier: MIT
2+
3+syntax = "proto3";
4+
5+package ermite;
6+
7+import "google/protobuf/timestamp.proto";
8+
9+// MirrorService configures the push mirrors of the server. Global settings
10+// and key
11+// rotation need the admin token or a token with every scope and no repository
12+// or
13+// branch limits; per-repository settings and SyncMirror need repo:admin on
14+// the
15+// repository; status needs read access. GetMirrorKey needs a non-anonymous
16+// caller.
17+// ListMirrorStatus and SyncMirror with All need owner rights. No new token
18+// scope
19+// is required. Servers implementing this service list "mirror" in
20+// GetVersionResponse.features.
21+service MirrorService {
22+ // GetMirrorKey returns the public mirror key, generating it on first use.
23+ // Needs a non-anonymous caller. Fails with failed_precondition without
24+ // ERMITE_SECRETS_KEY.
25+ rpc GetMirrorKey(GetMirrorKeyRequest) returns (GetMirrorKeyResponse);
26+ // RotateMirrorKey replaces the key immediately. Add the new key to remotes
27+ // and sync with force to recover from authentication failures.
28+ rpc RotateMirrorKey(RotateMirrorKeyRequest) returns (RotateMirrorKeyResponse);
29+ // GetMirrorConfig returns the global mirror policy.
30+ rpc GetMirrorConfig(GetMirrorConfigRequest) returns (GetMirrorConfigResponse);
31+ // SetMirrorConfig patches the global policy. Unset fields are unchanged.
32+ // Invalid fields give invalid_argument with field violations.
33+ rpc SetMirrorConfig(SetMirrorConfigRequest) returns (SetMirrorConfigResponse);
34+ // SetRepoMirror sets an override, even when global mirroring is disabled.
35+ rpc SetRepoMirror(SetRepoMirrorRequest) returns (SetRepoMirrorResponse);
36+ // RemoveRepoMirror removes the override so the global policy applies again.
37+ rpc RemoveRepoMirror(RemoveRepoMirrorRequest) returns (RemoveRepoMirrorResponse);
38+ // SetRepoMirrorExcluded excludes or includes a repository in the global
39+ // policy. An explicit URL override takes precedence.
40+ rpc SetRepoMirrorExcluded(SetRepoMirrorExcludedRequest) returns (SetRepoMirrorExcludedResponse);
41+ // GetMirrorStatus returns the resolved target and status of one repository.
42+ rpc GetMirrorStatus(GetMirrorStatusRequest) returns (GetMirrorStatusResponse);
43+ // ListMirrorStatus lists mirror statuses and needs owner rights.
44+ rpc ListMirrorStatus(ListMirrorStatusRequest) returns (ListMirrorStatusResponse);
45+ // SyncMirror syncs Repo or, with All, every repository. Paused mirrors need
46+ // Force. Manual requests without Force are at least 60 seconds apart.
47+ rpc SyncMirror(SyncMirrorRequest) returns (SyncMirrorResponse);
48+}
49+
50+// MirrorKey is the public SSH key used for push mirrors. Private key material
51+// is never returned.
52+message MirrorKey {
53+ // AuthorizedKey is the SSH authorized_keys line.
54+ string authorized_key = 1;
55+ // Fingerprint is the SHA256 fingerprint of the public key.
56+ string fingerprint = 2;
57+ // CreatedAt is when the key was created.
58+ google.protobuf.Timestamp created_at = 3;
59+}
60+
61+// GetMirrorKeyRequest selects the input of GetMirrorKey.
62+message GetMirrorKeyRequest {}
63+
64+// GetMirrorKeyResponse holds the result of GetMirrorKey.
65+message GetMirrorKeyResponse {
66+ // Key is the public mirror key.
67+ MirrorKey key = 1;
68+}
69+
70+// RotateMirrorKeyRequest selects the input of RotateMirrorKey.
71+message RotateMirrorKeyRequest {}
72+
73+// RotateMirrorKeyResponse holds the result of RotateMirrorKey.
74+message RotateMirrorKeyResponse {
75+ // Key is the public mirror key.
76+ MirrorKey key = 1;
77+ // PreviousFingerprint is the fingerprint of the replaced key.
78+ string previous_fingerprint = 2;
79+}
80+
81+// MirrorConfig is the server-wide push mirror policy.
82+message MirrorConfig {
83+ // Enabled says whether mirroring is enabled.
84+ bool enabled = 1;
85+ // URLTemplate is an SSH URL containing "{repo}", replaced with the
86+ // repository name.
87+ string url_template = 2;
88+ // Cron is a five-field cron expression in UTC, no more frequent than every
89+ // five minutes.
90+ string cron = 3;
91+ // AllBranches includes every branch rather than only the default branch.
92+ // Tags are always included.
93+ bool all_branches = 4;
94+ // UpdatedAt is when the configuration was last changed.
95+ google.protobuf.Timestamp updated_at = 5;
96+}
97+
98+// GetMirrorConfigRequest selects the input of GetMirrorConfig.
99+message GetMirrorConfigRequest {}
100+
101+// GetMirrorConfigResponse holds the result of GetMirrorConfig.
102+message GetMirrorConfigResponse {
103+ // Config is the global mirror policy.
104+ MirrorConfig config = 1;
105+}
106+
107+// SetMirrorConfigRequest selects the input of SetMirrorConfig.
108+message SetMirrorConfigRequest {
109+ // Enabled says whether mirroring is enabled. Unset means unchanged.
110+ optional bool enabled = 1;
111+ // URLTemplate is an SSH URL containing "{repo}", replaced with the
112+ // repository name. Unset means unchanged.
113+ optional string url_template = 2;
114+ // Cron is a five-field cron expression in UTC, no more frequent than every
115+ // five minutes. Unset means unchanged.
116+ optional string cron = 3;
117+ // AllBranches includes every branch rather than only the default branch.
118+ // Tags are always included. Unset means unchanged.
119+ optional bool all_branches = 4;
120+}
121+
122+// SetMirrorConfigResponse holds the result of SetMirrorConfig.
123+message SetMirrorConfigResponse {
124+ // Config is the global mirror policy.
125+ MirrorConfig config = 1;
126+}
127+
128+// MirrorSource says which configuration applies to a repository.
129+enum MirrorSource {
130+ // Unspecified.
131+ MIRROR_SOURCE_UNSPECIFIED = 0;
132+ // Disabled.
133+ MIRROR_SOURCE_DISABLED = 1;
134+ // Global.
135+ MIRROR_SOURCE_GLOBAL = 2;
136+ // Override.
137+ MIRROR_SOURCE_OVERRIDE = 3;
138+ // Excluded.
139+ MIRROR_SOURCE_EXCLUDED = 4;
140+}
141+
142+// MirrorTarget is the resolved push mirror target of a repository.
143+message MirrorTarget {
144+ // Repo is the repository name.
145+ string repo = 1;
146+ // Enabled says whether mirroring is enabled.
147+ bool enabled = 2;
148+ // URL is the SSH destination of the mirror.
149+ string url = 3;
150+ // Cron is a five-field cron expression in UTC, no more frequent than every
151+ // five minutes.
152+ string cron = 4;
153+ // AllBranches includes every branch rather than only the default branch.
154+ // Tags are always included.
155+ bool all_branches = 5;
156+ // Source is the configuration used to resolve the target.
157+ MirrorSource source = 6;
158+}
159+
160+// SetRepoMirrorRequest selects the input of SetRepoMirror.
161+message SetRepoMirrorRequest {
162+ // Repo is the repository name.
163+ string repo = 1;
164+ // URL is the SSH destination of the mirror.
165+ string url = 2;
166+ // Cron is a five-field cron expression in UTC, no more frequent than every
167+ // five minutes.
168+ // Empty means inherit the global cron.
169+ string cron = 3;
170+ // AllBranches includes every branch rather than only the default branch.
171+ // Tags are always included. Unset means inherit the global setting.
172+ optional bool all_branches = 4;
173+}
174+
175+// SetRepoMirrorResponse holds the result of SetRepoMirror.
176+message SetRepoMirrorResponse {
177+ // Target is the resolved mirror target.
178+ MirrorTarget target = 1;
179+}
180+
181+// RemoveRepoMirrorRequest selects the input of RemoveRepoMirror.
182+message RemoveRepoMirrorRequest {
183+ // Repo is the repository name.
184+ string repo = 1;
185+}
186+
187+// RemoveRepoMirrorResponse holds the result of RemoveRepoMirror.
188+message RemoveRepoMirrorResponse {}
189+
190+// SetRepoMirrorExcludedRequest selects the input of SetRepoMirrorExcluded.
191+message SetRepoMirrorExcludedRequest {
192+ // Repo is the repository name.
193+ string repo = 1;
194+ // Excluded opts out of the global template, unless a URL override exists.
195+ bool excluded = 2;
196+}
197+
198+// SetRepoMirrorExcludedResponse holds the result of SetRepoMirrorExcluded.
199+message SetRepoMirrorExcludedResponse {
200+ // Target is the resolved mirror target.
201+ MirrorTarget target = 1;
202+}
203+
204+// MirrorState is the current state of a push mirror.
205+enum MirrorState {
206+ // Unspecified.
207+ MIRROR_STATE_UNSPECIFIED = 0;
208+ // Disabled.
209+ MIRROR_STATE_DISABLED = 1;
210+ // Ok.
211+ MIRROR_STATE_OK = 2;
212+ // Failing.
213+ MIRROR_STATE_FAILING = 3;
214+ // Paused.
215+ MIRROR_STATE_PAUSED = 4;
216+}
217+
218+// MirrorStatus describes a repository mirror and its last attempts.
219+message MirrorStatus {
220+ // Target is the resolved mirror target.
221+ MirrorTarget target = 1;
222+ // State is the current mirror state.
223+ MirrorState state = 2;
224+ // FailureClass is the failure category, empty when there is no failure.
225+ string failure_class = 3;
226+ // Reason explains the current state.
227+ string reason = 4;
228+ // Detail is the last error detail, returned only to callers with repo:admin
229+ // on the repository.
230+ string detail = 5;
231+ // ConsecutiveFailures counts failed attempts since the last success or
232+ // reset.
233+ int32 consecutive_failures = 6;
234+ // LastAttemptAt is when the last sync was attempted.
235+ google.protobuf.Timestamp last_attempt_at = 7;
236+ // LastSuccessAt is when the last sync succeeded, including a no-op.
237+ google.protobuf.Timestamp last_success_at = 8;
238+ // NextRetryAt is the next retry time while failing; otherwise unset.
239+ google.protobuf.Timestamp next_retry_at = 9;
240+ // NextRunAt is the next scheduled sync time, or the retry time while
241+ // failing.
242+ google.protobuf.Timestamp next_run_at = 10;
243+ // PausedAt is when the mirror was paused; otherwise unset.
244+ google.protobuf.Timestamp paused_at = 11;
245+}
246+
247+// GetMirrorStatusRequest selects the input of GetMirrorStatus.
248+message GetMirrorStatusRequest {
249+ // Repo is the repository name.
250+ string repo = 1;
251+}
252+
253+// GetMirrorStatusResponse holds the result of GetMirrorStatus.
254+message GetMirrorStatusResponse {
255+ // Status is the current mirror status.
256+ MirrorStatus status = 1;
257+}
258+
259+// ListMirrorStatusRequest selects the input of ListMirrorStatus.
260+message ListMirrorStatusRequest {}
261+
262+// ListMirrorStatusResponse holds the result of ListMirrorStatus.
263+message ListMirrorStatusResponse {
264+ // Statuses are the mirror statuses.
265+ repeated MirrorStatus statuses = 1;
266+}
267+
268+// SyncMirrorRequest selects the input of SyncMirror.
269+message SyncMirrorRequest {
270+ // Repo is the repository name.
271+ string repo = 1;
272+ // All selects every repository and needs owner rights. Repo must be empty
273+ // when true.
274+ bool all = 2;
275+ // Force lifts a pause and skips the no-op check and the 60-second manual
276+ // throttle. It never forces a Git push.
277+ bool force = 3;
278+}
279+
280+// SyncMirrorResult is the result of a sync request for one repository.
281+message SyncMirrorResult {
282+ // Repo is the repository name.
283+ string repo = 1;
284+ // Queued says another sync is running and this request was queued.
285+ bool queued = 2;
286+ // NoOp says the refs already matched the last successful snapshot.
287+ bool no_op = 3;
288+ // Status is the current mirror status.
289+ MirrorStatus status = 4;
290+}
291+
292+// SyncMirrorResponse holds the result of SyncMirror.
293+message SyncMirrorResponse {
294+ // Results are the per-repository sync results.
295+ repeated SyncMirrorResult results = 1;
296+}

Keyboard shortcuts

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

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

Branches 2

Tags 18

↑↓ moveenter openesc close