detached d244a03

feat(proto): list, disable and enable workflows per repository

commit
d244a03090e3dae702b8b02360979fc00b28aa45
author
brice · · 2026-10-18 19:12 UTC
parent
4269eaf
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
refs
feat/workflow-disabletag: v0.7.0
5 files · +822 -116
M docs/services.md +1 -1
@@ -10,7 +10,7 @@
1010 | `EventService` | `Watch` (streamed, resumable) |
1111 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
1212 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
13-| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
13+| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
1414 | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed), `CompleteJob` (idempotent), `ResolveActions`, `UploadSummary` |
1515 | `SecretService` | `SetSecret`, `ListSecrets` (names only), `DeleteSecret`. Scopes: instance, repository, environment |
1616 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`. Same scopes |
@@ -10,7 +10,7 @@
10 | `EventService` | `Watch` (streamed, resumable) |10 | `EventService` | `Watch` (streamed, resumable) |
11 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |11 | `ServerService` | `GetVersion` (version and features), `GetSigningKey`, `RotateOIDCKey` (owner only) |
12 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |12 | `TrustedKeyService` | `ListTrustedKeys`, `AddTrustedKey` and `RemoveTrustedKey` (owner only). `Commit.verification` carries the result of the check (feature `commit_signatures`) |
13-| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |13+| `RunService` | `ListRuns`, `GetRun`, `GetJob`, `GetJobLog` (streamed, follow), `ListAnnotations`, `GetStepSummaries`, `CancelRun`, `RerunRun`, `DispatchWorkflow`, `ListWorkflows`, `DisableWorkflow`, `EnableWorkflow`, `ListChecks`, `ListRunners`, `RemoveRunner` |
14 | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed), `CompleteJob` (idempotent), `ResolveActions`, `UploadSummary` |14 | `RunnerService` | `RegisterRunner`, `FetchJob` (long poll), `UpdateJob`, `AppendLog` (offset-idempotent), `Heartbeat`, `WatchJob` (cancel requests, streamed), `CompleteJob` (idempotent), `ResolveActions`, `UploadSummary` |
15 | `SecretService` | `SetSecret`, `ListSecrets` (names only), `DeleteSecret`. Scopes: instance, repository, environment |15 | `SecretService` | `SetSecret`, `ListSecrets` (names only), `DeleteSecret`. Scopes: instance, repository, environment |
16 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`. Same scopes |16 | `VariableService` | `SetVariable`, `GetVariable`, `ListVariables`, `DeleteVariable`. Same scopes |
M gen/ermite/ermiteconnect/run.connect.go +141 -0
@@ -56,6 +56,15 @@ const (
5656 // RunServiceDispatchWorkflowProcedure is the fully-qualified name of the RunService's
5757 // DispatchWorkflow RPC.
5858 RunServiceDispatchWorkflowProcedure = "/ermite.RunService/DispatchWorkflow"
59+ // RunServiceListWorkflowsProcedure is the fully-qualified name of the RunService's ListWorkflows
60+ // RPC.
61+ RunServiceListWorkflowsProcedure = "/ermite.RunService/ListWorkflows"
62+ // RunServiceDisableWorkflowProcedure is the fully-qualified name of the RunService's
63+ // DisableWorkflow RPC.
64+ RunServiceDisableWorkflowProcedure = "/ermite.RunService/DisableWorkflow"
65+ // RunServiceEnableWorkflowProcedure is the fully-qualified name of the RunService's EnableWorkflow
66+ // RPC.
67+ RunServiceEnableWorkflowProcedure = "/ermite.RunService/EnableWorkflow"
5968 // RunServiceListChecksProcedure is the fully-qualified name of the RunService's ListChecks RPC.
6069 RunServiceListChecksProcedure = "/ermite.RunService/ListChecks"
6170 // RunServiceListRunnersProcedure is the fully-qualified name of the RunService's ListRunners RPC.
@@ -145,6 +154,36 @@ type RunServiceClient interface {
145154 // missing while required without default, or has a value of the wrong type
146155 // or outside its choices, or if the workflow file is not valid.
147156 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)
157+ // ListWorkflows lists the workflow files of a repository with their state,
158+ // like `gh workflow list --all`, ordered by path: the files of
159+ // .github/workflows at the tip of the default branch, and the files that are
160+ // disabled even if the default branch has no such file (a repository that
161+ // is empty, or a file not pushed yet). Needs the same access as reading the
162+ // repository. A repository without a default branch lists only its disabled
163+ // files. Only servers with the feature "ci.workflows" implement it
164+ // (unimplemented otherwise). Fails with not_found if the repository does not
165+ // exist or cannot be read.
166+ ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error)
167+ // DisableWorkflow disables a workflow file of a repository, like `gh
168+ // workflow disable`: no event starts a run of it any more (push,
169+ // pull_request, pull_request_target, schedule, workflow_run, create,
170+ // delete, and workflow_dispatch). Runs already started are not affected.
171+ // The state belongs to the repository and to the file path, not to a
172+ // commit or a branch, and the file does not have to exist: it can be set on
173+ // an empty repository, before the first push. Disabling a disabled workflow
174+ // succeeds and changes nothing. Needs the repo:admin scope on the
175+ // repository, and a token that is not limited to branch prefixes. Only
176+ // servers with the feature "ci.workflows" implement it (unimplemented
177+ // otherwise). Fails with not_found if the repository does not exist or the
178+ // caller cannot read it, permission_denied without the scope, and
179+ // invalid_argument if the workflow is not a file name or a path of
180+ // .github/workflows ending in .yml or .yaml.
181+ DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error)
182+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
183+ // a workflow that is not disabled succeeds and changes nothing. Same access
184+ // and errors as DisableWorkflow. Events that happened while the workflow was
185+ // disabled are not replayed.
186+ EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error)
148187 // ListChecks lists the checks of a commit: for each job of each run on that
149188 // sha, the one of the latest attempt, ordered by creation time then name.
150189 // A commit without a run has no check: the list is empty, which is not an
@@ -227,6 +266,24 @@ func NewRunServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...
227266 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),
228267 connect.WithClientOptions(opts...),
229268 ),
269+ listWorkflows: connect.NewClient[ermite.ListWorkflowsRequest, ermite.ListWorkflowsResponse](
270+ httpClient,
271+ baseURL+RunServiceListWorkflowsProcedure,
272+ connect.WithSchema(runServiceMethods.ByName("ListWorkflows")),
273+ connect.WithClientOptions(opts...),
274+ ),
275+ disableWorkflow: connect.NewClient[ermite.DisableWorkflowRequest, ermite.DisableWorkflowResponse](
276+ httpClient,
277+ baseURL+RunServiceDisableWorkflowProcedure,
278+ connect.WithSchema(runServiceMethods.ByName("DisableWorkflow")),
279+ connect.WithClientOptions(opts...),
280+ ),
281+ enableWorkflow: connect.NewClient[ermite.EnableWorkflowRequest, ermite.EnableWorkflowResponse](
282+ httpClient,
283+ baseURL+RunServiceEnableWorkflowProcedure,
284+ connect.WithSchema(runServiceMethods.ByName("EnableWorkflow")),
285+ connect.WithClientOptions(opts...),
286+ ),
230287 listChecks: connect.NewClient[ermite.ListChecksRequest, ermite.ListChecksResponse](
231288 httpClient,
232289 baseURL+RunServiceListChecksProcedure,
@@ -259,6 +316,9 @@ type runServiceClient struct {
259316 cancelRun *connect.Client[ermite.CancelRunRequest, ermite.CancelRunResponse]
260317 rerunRun *connect.Client[ermite.RerunRunRequest, ermite.RerunRunResponse]
261318 dispatchWorkflow *connect.Client[ermite.DispatchWorkflowRequest, ermite.DispatchWorkflowResponse]
319+ listWorkflows *connect.Client[ermite.ListWorkflowsRequest, ermite.ListWorkflowsResponse]
320+ disableWorkflow *connect.Client[ermite.DisableWorkflowRequest, ermite.DisableWorkflowResponse]
321+ enableWorkflow *connect.Client[ermite.EnableWorkflowRequest, ermite.EnableWorkflowResponse]
262322 listChecks *connect.Client[ermite.ListChecksRequest, ermite.ListChecksResponse]
263323 listRunners *connect.Client[ermite.ListRunnersRequest, ermite.ListRunnersResponse]
264324 removeRunner *connect.Client[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse]
@@ -309,6 +369,21 @@ func (c *runServiceClient) DispatchWorkflow(ctx context.Context, req *connect.Re
309369 return c.dispatchWorkflow.CallUnary(ctx, req)
310370 }
311371
372+// ListWorkflows calls ermite.RunService.ListWorkflows.
373+func (c *runServiceClient) ListWorkflows(ctx context.Context, req *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error) {
374+ return c.listWorkflows.CallUnary(ctx, req)
375+}
376+
377+// DisableWorkflow calls ermite.RunService.DisableWorkflow.
378+func (c *runServiceClient) DisableWorkflow(ctx context.Context, req *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error) {
379+ return c.disableWorkflow.CallUnary(ctx, req)
380+}
381+
382+// EnableWorkflow calls ermite.RunService.EnableWorkflow.
383+func (c *runServiceClient) EnableWorkflow(ctx context.Context, req *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error) {
384+ return c.enableWorkflow.CallUnary(ctx, req)
385+}
386+
312387 // ListChecks calls ermite.RunService.ListChecks.
313388 func (c *runServiceClient) ListChecks(ctx context.Context, req *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
314389 return c.listChecks.CallUnary(ctx, req)
@@ -405,6 +480,36 @@ type RunServiceHandler interface {
405480 // missing while required without default, or has a value of the wrong type
406481 // or outside its choices, or if the workflow file is not valid.
407482 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)
483+ // ListWorkflows lists the workflow files of a repository with their state,
484+ // like `gh workflow list --all`, ordered by path: the files of
485+ // .github/workflows at the tip of the default branch, and the files that are
486+ // disabled even if the default branch has no such file (a repository that
487+ // is empty, or a file not pushed yet). Needs the same access as reading the
488+ // repository. A repository without a default branch lists only its disabled
489+ // files. Only servers with the feature "ci.workflows" implement it
490+ // (unimplemented otherwise). Fails with not_found if the repository does not
491+ // exist or cannot be read.
492+ ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error)
493+ // DisableWorkflow disables a workflow file of a repository, like `gh
494+ // workflow disable`: no event starts a run of it any more (push,
495+ // pull_request, pull_request_target, schedule, workflow_run, create,
496+ // delete, and workflow_dispatch). Runs already started are not affected.
497+ // The state belongs to the repository and to the file path, not to a
498+ // commit or a branch, and the file does not have to exist: it can be set on
499+ // an empty repository, before the first push. Disabling a disabled workflow
500+ // succeeds and changes nothing. Needs the repo:admin scope on the
501+ // repository, and a token that is not limited to branch prefixes. Only
502+ // servers with the feature "ci.workflows" implement it (unimplemented
503+ // otherwise). Fails with not_found if the repository does not exist or the
504+ // caller cannot read it, permission_denied without the scope, and
505+ // invalid_argument if the workflow is not a file name or a path of
506+ // .github/workflows ending in .yml or .yaml.
507+ DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error)
508+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
509+ // a workflow that is not disabled succeeds and changes nothing. Same access
510+ // and errors as DisableWorkflow. Events that happened while the workflow was
511+ // disabled are not replayed.
512+ EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error)
408513 // ListChecks lists the checks of a commit: for each job of each run on that
409514 // sha, the one of the latest attempt, ordered by creation time then name.
410515 // A commit without a run has no check: the list is empty, which is not an
@@ -483,6 +588,24 @@ func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption)
483588 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),
484589 connect.WithHandlerOptions(opts...),
485590 )
591+ runServiceListWorkflowsHandler := connect.NewUnaryHandler(
592+ RunServiceListWorkflowsProcedure,
593+ svc.ListWorkflows,
594+ connect.WithSchema(runServiceMethods.ByName("ListWorkflows")),
595+ connect.WithHandlerOptions(opts...),
596+ )
597+ runServiceDisableWorkflowHandler := connect.NewUnaryHandler(
598+ RunServiceDisableWorkflowProcedure,
599+ svc.DisableWorkflow,
600+ connect.WithSchema(runServiceMethods.ByName("DisableWorkflow")),
601+ connect.WithHandlerOptions(opts...),
602+ )
603+ runServiceEnableWorkflowHandler := connect.NewUnaryHandler(
604+ RunServiceEnableWorkflowProcedure,
605+ svc.EnableWorkflow,
606+ connect.WithSchema(runServiceMethods.ByName("EnableWorkflow")),
607+ connect.WithHandlerOptions(opts...),
608+ )
486609 runServiceListChecksHandler := connect.NewUnaryHandler(
487610 RunServiceListChecksProcedure,
488611 svc.ListChecks,
@@ -521,6 +644,12 @@ func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption)
521644 runServiceRerunRunHandler.ServeHTTP(w, r)
522645 case RunServiceDispatchWorkflowProcedure:
523646 runServiceDispatchWorkflowHandler.ServeHTTP(w, r)
647+ case RunServiceListWorkflowsProcedure:
648+ runServiceListWorkflowsHandler.ServeHTTP(w, r)
649+ case RunServiceDisableWorkflowProcedure:
650+ runServiceDisableWorkflowHandler.ServeHTTP(w, r)
651+ case RunServiceEnableWorkflowProcedure:
652+ runServiceEnableWorkflowHandler.ServeHTTP(w, r)
524653 case RunServiceListChecksProcedure:
525654 runServiceListChecksHandler.ServeHTTP(w, r)
526655 case RunServiceListRunnersProcedure:
@@ -572,6 +701,18 @@ func (UnimplementedRunServiceHandler) DispatchWorkflow(context.Context, *connect
572701 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.DispatchWorkflow is not implemented"))
573702 }
574703
704+func (UnimplementedRunServiceHandler) ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error) {
705+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListWorkflows is not implemented"))
706+}
707+
708+func (UnimplementedRunServiceHandler) DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error) {
709+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.DisableWorkflow is not implemented"))
710+}
711+
712+func (UnimplementedRunServiceHandler) EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error) {
713+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.EnableWorkflow is not implemented"))
714+}
715+
575716 func (UnimplementedRunServiceHandler) ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
576717 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListChecks is not implemented"))
577718 }
@@ -56,6 +56,15 @@ const (
56 // RunServiceDispatchWorkflowProcedure is the fully-qualified name of the RunService's56 // RunServiceDispatchWorkflowProcedure is the fully-qualified name of the RunService's
57 // DispatchWorkflow RPC.57 // DispatchWorkflow RPC.
58 RunServiceDispatchWorkflowProcedure = "/ermite.RunService/DispatchWorkflow"58 RunServiceDispatchWorkflowProcedure = "/ermite.RunService/DispatchWorkflow"
59+ // RunServiceListWorkflowsProcedure is the fully-qualified name of the RunService's ListWorkflows
60+ // RPC.
61+ RunServiceListWorkflowsProcedure = "/ermite.RunService/ListWorkflows"
62+ // RunServiceDisableWorkflowProcedure is the fully-qualified name of the RunService's
63+ // DisableWorkflow RPC.
64+ RunServiceDisableWorkflowProcedure = "/ermite.RunService/DisableWorkflow"
65+ // RunServiceEnableWorkflowProcedure is the fully-qualified name of the RunService's EnableWorkflow
66+ // RPC.
67+ RunServiceEnableWorkflowProcedure = "/ermite.RunService/EnableWorkflow"
59 // RunServiceListChecksProcedure is the fully-qualified name of the RunService's ListChecks RPC.68 // RunServiceListChecksProcedure is the fully-qualified name of the RunService's ListChecks RPC.
60 RunServiceListChecksProcedure = "/ermite.RunService/ListChecks"69 RunServiceListChecksProcedure = "/ermite.RunService/ListChecks"
61 // RunServiceListRunnersProcedure is the fully-qualified name of the RunService's ListRunners RPC.70 // RunServiceListRunnersProcedure is the fully-qualified name of the RunService's ListRunners RPC.
@@ -145,6 +154,36 @@ type RunServiceClient interface {
145 // missing while required without default, or has a value of the wrong type154 // missing while required without default, or has a value of the wrong type
146 // or outside its choices, or if the workflow file is not valid.155 // or outside its choices, or if the workflow file is not valid.
147 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)156 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)
157+ // ListWorkflows lists the workflow files of a repository with their state,
158+ // like `gh workflow list --all`, ordered by path: the files of
159+ // .github/workflows at the tip of the default branch, and the files that are
160+ // disabled even if the default branch has no such file (a repository that
161+ // is empty, or a file not pushed yet). Needs the same access as reading the
162+ // repository. A repository without a default branch lists only its disabled
163+ // files. Only servers with the feature "ci.workflows" implement it
164+ // (unimplemented otherwise). Fails with not_found if the repository does not
165+ // exist or cannot be read.
166+ ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error)
167+ // DisableWorkflow disables a workflow file of a repository, like `gh
168+ // workflow disable`: no event starts a run of it any more (push,
169+ // pull_request, pull_request_target, schedule, workflow_run, create,
170+ // delete, and workflow_dispatch). Runs already started are not affected.
171+ // The state belongs to the repository and to the file path, not to a
172+ // commit or a branch, and the file does not have to exist: it can be set on
173+ // an empty repository, before the first push. Disabling a disabled workflow
174+ // succeeds and changes nothing. Needs the repo:admin scope on the
175+ // repository, and a token that is not limited to branch prefixes. Only
176+ // servers with the feature "ci.workflows" implement it (unimplemented
177+ // otherwise). Fails with not_found if the repository does not exist or the
178+ // caller cannot read it, permission_denied without the scope, and
179+ // invalid_argument if the workflow is not a file name or a path of
180+ // .github/workflows ending in .yml or .yaml.
181+ DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error)
182+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
183+ // a workflow that is not disabled succeeds and changes nothing. Same access
184+ // and errors as DisableWorkflow. Events that happened while the workflow was
185+ // disabled are not replayed.
186+ EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error)
148 // ListChecks lists the checks of a commit: for each job of each run on that187 // ListChecks lists the checks of a commit: for each job of each run on that
149 // sha, the one of the latest attempt, ordered by creation time then name.188 // sha, the one of the latest attempt, ordered by creation time then name.
150 // A commit without a run has no check: the list is empty, which is not an189 // A commit without a run has no check: the list is empty, which is not an
@@ -227,6 +266,24 @@ func NewRunServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...
227 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),266 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),
228 connect.WithClientOptions(opts...),267 connect.WithClientOptions(opts...),
229 ),268 ),
269+ listWorkflows: connect.NewClient[ermite.ListWorkflowsRequest, ermite.ListWorkflowsResponse](
270+ httpClient,
271+ baseURL+RunServiceListWorkflowsProcedure,
272+ connect.WithSchema(runServiceMethods.ByName("ListWorkflows")),
273+ connect.WithClientOptions(opts...),
274+ ),
275+ disableWorkflow: connect.NewClient[ermite.DisableWorkflowRequest, ermite.DisableWorkflowResponse](
276+ httpClient,
277+ baseURL+RunServiceDisableWorkflowProcedure,
278+ connect.WithSchema(runServiceMethods.ByName("DisableWorkflow")),
279+ connect.WithClientOptions(opts...),
280+ ),
281+ enableWorkflow: connect.NewClient[ermite.EnableWorkflowRequest, ermite.EnableWorkflowResponse](
282+ httpClient,
283+ baseURL+RunServiceEnableWorkflowProcedure,
284+ connect.WithSchema(runServiceMethods.ByName("EnableWorkflow")),
285+ connect.WithClientOptions(opts...),
286+ ),
230 listChecks: connect.NewClient[ermite.ListChecksRequest, ermite.ListChecksResponse](287 listChecks: connect.NewClient[ermite.ListChecksRequest, ermite.ListChecksResponse](
231 httpClient,288 httpClient,
232 baseURL+RunServiceListChecksProcedure,289 baseURL+RunServiceListChecksProcedure,
@@ -259,6 +316,9 @@ type runServiceClient struct {
259 cancelRun *connect.Client[ermite.CancelRunRequest, ermite.CancelRunResponse]316 cancelRun *connect.Client[ermite.CancelRunRequest, ermite.CancelRunResponse]
260 rerunRun *connect.Client[ermite.RerunRunRequest, ermite.RerunRunResponse]317 rerunRun *connect.Client[ermite.RerunRunRequest, ermite.RerunRunResponse]
261 dispatchWorkflow *connect.Client[ermite.DispatchWorkflowRequest, ermite.DispatchWorkflowResponse]318 dispatchWorkflow *connect.Client[ermite.DispatchWorkflowRequest, ermite.DispatchWorkflowResponse]
319+ listWorkflows *connect.Client[ermite.ListWorkflowsRequest, ermite.ListWorkflowsResponse]
320+ disableWorkflow *connect.Client[ermite.DisableWorkflowRequest, ermite.DisableWorkflowResponse]
321+ enableWorkflow *connect.Client[ermite.EnableWorkflowRequest, ermite.EnableWorkflowResponse]
262 listChecks *connect.Client[ermite.ListChecksRequest, ermite.ListChecksResponse]322 listChecks *connect.Client[ermite.ListChecksRequest, ermite.ListChecksResponse]
263 listRunners *connect.Client[ermite.ListRunnersRequest, ermite.ListRunnersResponse]323 listRunners *connect.Client[ermite.ListRunnersRequest, ermite.ListRunnersResponse]
264 removeRunner *connect.Client[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse]324 removeRunner *connect.Client[ermite.RemoveRunnerRequest, ermite.RemoveRunnerResponse]
@@ -309,6 +369,21 @@ func (c *runServiceClient) DispatchWorkflow(ctx context.Context, req *connect.Re
309 return c.dispatchWorkflow.CallUnary(ctx, req)369 return c.dispatchWorkflow.CallUnary(ctx, req)
310 }370 }
311 371
372+// ListWorkflows calls ermite.RunService.ListWorkflows.
373+func (c *runServiceClient) ListWorkflows(ctx context.Context, req *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error) {
374+ return c.listWorkflows.CallUnary(ctx, req)
375+}
376+
377+// DisableWorkflow calls ermite.RunService.DisableWorkflow.
378+func (c *runServiceClient) DisableWorkflow(ctx context.Context, req *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error) {
379+ return c.disableWorkflow.CallUnary(ctx, req)
380+}
381+
382+// EnableWorkflow calls ermite.RunService.EnableWorkflow.
383+func (c *runServiceClient) EnableWorkflow(ctx context.Context, req *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error) {
384+ return c.enableWorkflow.CallUnary(ctx, req)
385+}
386+
312 // ListChecks calls ermite.RunService.ListChecks.387 // ListChecks calls ermite.RunService.ListChecks.
313 func (c *runServiceClient) ListChecks(ctx context.Context, req *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {388 func (c *runServiceClient) ListChecks(ctx context.Context, req *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
314 return c.listChecks.CallUnary(ctx, req)389 return c.listChecks.CallUnary(ctx, req)
@@ -405,6 +480,36 @@ type RunServiceHandler interface {
405 // missing while required without default, or has a value of the wrong type480 // missing while required without default, or has a value of the wrong type
406 // or outside its choices, or if the workflow file is not valid.481 // or outside its choices, or if the workflow file is not valid.
407 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)482 DispatchWorkflow(context.Context, *connect.Request[ermite.DispatchWorkflowRequest]) (*connect.Response[ermite.DispatchWorkflowResponse], error)
483+ // ListWorkflows lists the workflow files of a repository with their state,
484+ // like `gh workflow list --all`, ordered by path: the files of
485+ // .github/workflows at the tip of the default branch, and the files that are
486+ // disabled even if the default branch has no such file (a repository that
487+ // is empty, or a file not pushed yet). Needs the same access as reading the
488+ // repository. A repository without a default branch lists only its disabled
489+ // files. Only servers with the feature "ci.workflows" implement it
490+ // (unimplemented otherwise). Fails with not_found if the repository does not
491+ // exist or cannot be read.
492+ ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error)
493+ // DisableWorkflow disables a workflow file of a repository, like `gh
494+ // workflow disable`: no event starts a run of it any more (push,
495+ // pull_request, pull_request_target, schedule, workflow_run, create,
496+ // delete, and workflow_dispatch). Runs already started are not affected.
497+ // The state belongs to the repository and to the file path, not to a
498+ // commit or a branch, and the file does not have to exist: it can be set on
499+ // an empty repository, before the first push. Disabling a disabled workflow
500+ // succeeds and changes nothing. Needs the repo:admin scope on the
501+ // repository, and a token that is not limited to branch prefixes. Only
502+ // servers with the feature "ci.workflows" implement it (unimplemented
503+ // otherwise). Fails with not_found if the repository does not exist or the
504+ // caller cannot read it, permission_denied without the scope, and
505+ // invalid_argument if the workflow is not a file name or a path of
506+ // .github/workflows ending in .yml or .yaml.
507+ DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error)
508+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
509+ // a workflow that is not disabled succeeds and changes nothing. Same access
510+ // and errors as DisableWorkflow. Events that happened while the workflow was
511+ // disabled are not replayed.
512+ EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error)
408 // ListChecks lists the checks of a commit: for each job of each run on that513 // ListChecks lists the checks of a commit: for each job of each run on that
409 // sha, the one of the latest attempt, ordered by creation time then name.514 // sha, the one of the latest attempt, ordered by creation time then name.
410 // A commit without a run has no check: the list is empty, which is not an515 // A commit without a run has no check: the list is empty, which is not an
@@ -483,6 +588,24 @@ func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption)
483 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),588 connect.WithSchema(runServiceMethods.ByName("DispatchWorkflow")),
484 connect.WithHandlerOptions(opts...),589 connect.WithHandlerOptions(opts...),
485 )590 )
591+ runServiceListWorkflowsHandler := connect.NewUnaryHandler(
592+ RunServiceListWorkflowsProcedure,
593+ svc.ListWorkflows,
594+ connect.WithSchema(runServiceMethods.ByName("ListWorkflows")),
595+ connect.WithHandlerOptions(opts...),
596+ )
597+ runServiceDisableWorkflowHandler := connect.NewUnaryHandler(
598+ RunServiceDisableWorkflowProcedure,
599+ svc.DisableWorkflow,
600+ connect.WithSchema(runServiceMethods.ByName("DisableWorkflow")),
601+ connect.WithHandlerOptions(opts...),
602+ )
603+ runServiceEnableWorkflowHandler := connect.NewUnaryHandler(
604+ RunServiceEnableWorkflowProcedure,
605+ svc.EnableWorkflow,
606+ connect.WithSchema(runServiceMethods.ByName("EnableWorkflow")),
607+ connect.WithHandlerOptions(opts...),
608+ )
486 runServiceListChecksHandler := connect.NewUnaryHandler(609 runServiceListChecksHandler := connect.NewUnaryHandler(
487 RunServiceListChecksProcedure,610 RunServiceListChecksProcedure,
488 svc.ListChecks,611 svc.ListChecks,
@@ -521,6 +644,12 @@ func NewRunServiceHandler(svc RunServiceHandler, opts ...connect.HandlerOption)
521 runServiceRerunRunHandler.ServeHTTP(w, r)644 runServiceRerunRunHandler.ServeHTTP(w, r)
522 case RunServiceDispatchWorkflowProcedure:645 case RunServiceDispatchWorkflowProcedure:
523 runServiceDispatchWorkflowHandler.ServeHTTP(w, r)646 runServiceDispatchWorkflowHandler.ServeHTTP(w, r)
647+ case RunServiceListWorkflowsProcedure:
648+ runServiceListWorkflowsHandler.ServeHTTP(w, r)
649+ case RunServiceDisableWorkflowProcedure:
650+ runServiceDisableWorkflowHandler.ServeHTTP(w, r)
651+ case RunServiceEnableWorkflowProcedure:
652+ runServiceEnableWorkflowHandler.ServeHTTP(w, r)
524 case RunServiceListChecksProcedure:653 case RunServiceListChecksProcedure:
525 runServiceListChecksHandler.ServeHTTP(w, r)654 runServiceListChecksHandler.ServeHTTP(w, r)
526 case RunServiceListRunnersProcedure:655 case RunServiceListRunnersProcedure:
@@ -572,6 +701,18 @@ func (UnimplementedRunServiceHandler) DispatchWorkflow(context.Context, *connect
572 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.DispatchWorkflow is not implemented"))701 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.DispatchWorkflow is not implemented"))
573 }702 }
574 703
704+func (UnimplementedRunServiceHandler) ListWorkflows(context.Context, *connect.Request[ermite.ListWorkflowsRequest]) (*connect.Response[ermite.ListWorkflowsResponse], error) {
705+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListWorkflows is not implemented"))
706+}
707+
708+func (UnimplementedRunServiceHandler) DisableWorkflow(context.Context, *connect.Request[ermite.DisableWorkflowRequest]) (*connect.Response[ermite.DisableWorkflowResponse], error) {
709+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.DisableWorkflow is not implemented"))
710+}
711+
712+func (UnimplementedRunServiceHandler) EnableWorkflow(context.Context, *connect.Request[ermite.EnableWorkflowRequest]) (*connect.Response[ermite.EnableWorkflowResponse], error) {
713+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.EnableWorkflow is not implemented"))
714+}
715+
575 func (UnimplementedRunServiceHandler) ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {716 func (UnimplementedRunServiceHandler) ListChecks(context.Context, *connect.Request[ermite.ListChecksRequest]) (*connect.Response[ermite.ListChecksResponse], error) {
576 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListChecks is not implemented"))717 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.RunService.ListChecks is not implemented"))
577 }718 }
M gen/ermite/run.pb.go +571 -113 generated file: collapsed
M gen_test.go +13 -0
@@ -66,6 +66,9 @@ func TestProcedures(t *testing.T) {
6666 {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},
6767 {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},
6868 {ermiteconnect.RunServiceDispatchWorkflowProcedure, "/ermite.RunService/DispatchWorkflow"},
69+ {ermiteconnect.RunServiceListWorkflowsProcedure, "/ermite.RunService/ListWorkflows"},
70+ {ermiteconnect.RunServiceDisableWorkflowProcedure, "/ermite.RunService/DisableWorkflow"},
71+ {ermiteconnect.RunServiceEnableWorkflowProcedure, "/ermite.RunService/EnableWorkflow"},
6972 {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},
7073 {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},
7174 {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},
@@ -489,6 +492,13 @@ func TestCI3FieldNumbers(t *testing.T) {
489492 {&ermite.RunJob{}, map[string]int{"concurrency_group": 20}},
490493 {&ermite.DispatchWorkflowRequest{}, map[string]int{"repo": 1, "workflow_path": 2, "ref": 3, "inputs": 4}},
491494 {&ermite.DispatchWorkflowResponse{}, map[string]int{"run": 1}},
495+ {&ermite.Workflow{}, map[string]int{"path": 1, "name": 2, "state": 3}},
496+ {&ermite.ListWorkflowsRequest{}, map[string]int{"repo": 1}},
497+ {&ermite.ListWorkflowsResponse{}, map[string]int{"workflows": 1}},
498+ {&ermite.DisableWorkflowRequest{}, map[string]int{"repo": 1, "workflow": 2}},
499+ {&ermite.DisableWorkflowResponse{}, map[string]int{"workflow": 1}},
500+ {&ermite.EnableWorkflowRequest{}, map[string]int{"repo": 1, "workflow": 2}},
501+ {&ermite.EnableWorkflowResponse{}, map[string]int{"workflow": 1}},
492502 {&ermite.SettingsScope{}, map[string]int{"instance": 1, "repo": 2, "environment": 3}},
493503 {&ermite.EnvironmentRef{}, map[string]int{"repo": 1, "name": 2}},
494504 {&ermite.Secret{}, map[string]int{"name": 1, "updated_at": 2}},
@@ -527,6 +537,9 @@ func TestCI3RoundTrip(t *testing.T) {
527537 &ermite.RunJob{Id: "j1", Status: ermite.CiStatus_CI_STATUS_PENDING, ConcurrencyGroup: "deploy"},
528538 &ermite.DispatchWorkflowRequest{Repo: "ermite", WorkflowPath: "ci.yml", Ref: "main", Inputs: map[string]string{"debug": "true"}},
529539 &ermite.DispatchWorkflowResponse{Run: &ermite.Run{Id: 9, Event: "workflow_dispatch"}},
540+ &ermite.ListWorkflowsResponse{Workflows: []*ermite.Workflow{{Path: ".github/workflows/release.yml", Name: "Release", State: ermite.WorkflowState_WORKFLOW_STATE_DISABLED}}},
541+ &ermite.DisableWorkflowRequest{Repo: "ermite", Workflow: "release.yml"},
542+ &ermite.EnableWorkflowResponse{Workflow: &ermite.Workflow{Path: ".github/workflows/release.yml", State: ermite.WorkflowState_WORKFLOW_STATE_ENABLED}},
530543 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Instance{Instance: true}}, Name: "A", Value: "v"},
531544 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Repo{Repo: "ermite"}}, Name: "A", Value: "v"},
532545 &ermite.SetVariableRequest{
@@ -66,6 +66,9 @@ func TestProcedures(t *testing.T) {
66 {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},66 {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},
67 {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},67 {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},
68 {ermiteconnect.RunServiceDispatchWorkflowProcedure, "/ermite.RunService/DispatchWorkflow"},68 {ermiteconnect.RunServiceDispatchWorkflowProcedure, "/ermite.RunService/DispatchWorkflow"},
69+ {ermiteconnect.RunServiceListWorkflowsProcedure, "/ermite.RunService/ListWorkflows"},
70+ {ermiteconnect.RunServiceDisableWorkflowProcedure, "/ermite.RunService/DisableWorkflow"},
71+ {ermiteconnect.RunServiceEnableWorkflowProcedure, "/ermite.RunService/EnableWorkflow"},
69 {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},72 {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},
70 {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},73 {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},
71 {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},74 {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},
@@ -489,6 +492,13 @@ func TestCI3FieldNumbers(t *testing.T) {
489 {&ermite.RunJob{}, map[string]int{"concurrency_group": 20}},492 {&ermite.RunJob{}, map[string]int{"concurrency_group": 20}},
490 {&ermite.DispatchWorkflowRequest{}, map[string]int{"repo": 1, "workflow_path": 2, "ref": 3, "inputs": 4}},493 {&ermite.DispatchWorkflowRequest{}, map[string]int{"repo": 1, "workflow_path": 2, "ref": 3, "inputs": 4}},
491 {&ermite.DispatchWorkflowResponse{}, map[string]int{"run": 1}},494 {&ermite.DispatchWorkflowResponse{}, map[string]int{"run": 1}},
495+ {&ermite.Workflow{}, map[string]int{"path": 1, "name": 2, "state": 3}},
496+ {&ermite.ListWorkflowsRequest{}, map[string]int{"repo": 1}},
497+ {&ermite.ListWorkflowsResponse{}, map[string]int{"workflows": 1}},
498+ {&ermite.DisableWorkflowRequest{}, map[string]int{"repo": 1, "workflow": 2}},
499+ {&ermite.DisableWorkflowResponse{}, map[string]int{"workflow": 1}},
500+ {&ermite.EnableWorkflowRequest{}, map[string]int{"repo": 1, "workflow": 2}},
501+ {&ermite.EnableWorkflowResponse{}, map[string]int{"workflow": 1}},
492 {&ermite.SettingsScope{}, map[string]int{"instance": 1, "repo": 2, "environment": 3}},502 {&ermite.SettingsScope{}, map[string]int{"instance": 1, "repo": 2, "environment": 3}},
493 {&ermite.EnvironmentRef{}, map[string]int{"repo": 1, "name": 2}},503 {&ermite.EnvironmentRef{}, map[string]int{"repo": 1, "name": 2}},
494 {&ermite.Secret{}, map[string]int{"name": 1, "updated_at": 2}},504 {&ermite.Secret{}, map[string]int{"name": 1, "updated_at": 2}},
@@ -527,6 +537,9 @@ func TestCI3RoundTrip(t *testing.T) {
527 &ermite.RunJob{Id: "j1", Status: ermite.CiStatus_CI_STATUS_PENDING, ConcurrencyGroup: "deploy"},537 &ermite.RunJob{Id: "j1", Status: ermite.CiStatus_CI_STATUS_PENDING, ConcurrencyGroup: "deploy"},
528 &ermite.DispatchWorkflowRequest{Repo: "ermite", WorkflowPath: "ci.yml", Ref: "main", Inputs: map[string]string{"debug": "true"}},538 &ermite.DispatchWorkflowRequest{Repo: "ermite", WorkflowPath: "ci.yml", Ref: "main", Inputs: map[string]string{"debug": "true"}},
529 &ermite.DispatchWorkflowResponse{Run: &ermite.Run{Id: 9, Event: "workflow_dispatch"}},539 &ermite.DispatchWorkflowResponse{Run: &ermite.Run{Id: 9, Event: "workflow_dispatch"}},
540+ &ermite.ListWorkflowsResponse{Workflows: []*ermite.Workflow{{Path: ".github/workflows/release.yml", Name: "Release", State: ermite.WorkflowState_WORKFLOW_STATE_DISABLED}}},
541+ &ermite.DisableWorkflowRequest{Repo: "ermite", Workflow: "release.yml"},
542+ &ermite.EnableWorkflowResponse{Workflow: &ermite.Workflow{Path: ".github/workflows/release.yml", State: ermite.WorkflowState_WORKFLOW_STATE_ENABLED}},
530 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Instance{Instance: true}}, Name: "A", Value: "v"},543 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Instance{Instance: true}}, Name: "A", Value: "v"},
531 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Repo{Repo: "ermite"}}, Name: "A", Value: "v"},544 &ermite.SetSecretRequest{Scope: &ermite.SettingsScope{Where: &ermite.SettingsScope_Repo{Repo: "ermite"}}, Name: "A", Value: "v"},
532 &ermite.SetVariableRequest{545 &ermite.SetVariableRequest{
M proto/ermite/run.proto +96 -2
@@ -282,14 +282,16 @@ message Runner {
282282 // request (other fields must be unchanged).
283283 //
284284 // Servers that implement the runs side (every RPC but ListRunners,
285-// RemoveRunner, ListAnnotations, GetStepSummaries and DispatchWorkflow) list
285+// RemoveRunner, ListAnnotations, GetStepSummaries, DispatchWorkflow and the
286+// workflow state RPCs) list
286287 // "ci.runs" in
287288 // GetVersionResponse.features; those that
288289 // implement the runners side list "ci.runners", as RunnerService does. A server
289290 // that honours the filters of ListRunsRequest also lists "ci.runs.filters".
290291 // ListAnnotations needs "ci.annotations" and GetStepSummaries "ci.summaries",
291292 // which are also the features of the runner side of each. DispatchWorkflow
292-// needs "ci.dispatch". RunJob.concurrency_group and CI_STATUS_PENDING belong
293+// needs "ci.dispatch". ListWorkflows, DisableWorkflow and EnableWorkflow need
294+// "ci.workflows". RunJob.concurrency_group and CI_STATUS_PENDING belong
293295 // to "ci.concurrency", Run.trusted to "ci.secrets".
294296 service RunService {
295297 // ListRuns lists the runs of a repository, highest id (newest) first. Each
@@ -371,6 +373,36 @@ service RunService {
371373 // missing while required without default, or has a value of the wrong type
372374 // or outside its choices, or if the workflow file is not valid.
373375 rpc DispatchWorkflow(DispatchWorkflowRequest) returns (DispatchWorkflowResponse);
376+ // ListWorkflows lists the workflow files of a repository with their state,
377+ // like `gh workflow list --all`, ordered by path: the files of
378+ // .github/workflows at the tip of the default branch, and the files that are
379+ // disabled even if the default branch has no such file (a repository that
380+ // is empty, or a file not pushed yet). Needs the same access as reading the
381+ // repository. A repository without a default branch lists only its disabled
382+ // files. Only servers with the feature "ci.workflows" implement it
383+ // (unimplemented otherwise). Fails with not_found if the repository does not
384+ // exist or cannot be read.
385+ rpc ListWorkflows(ListWorkflowsRequest) returns (ListWorkflowsResponse);
386+ // DisableWorkflow disables a workflow file of a repository, like `gh
387+ // workflow disable`: no event starts a run of it any more (push,
388+ // pull_request, pull_request_target, schedule, workflow_run, create,
389+ // delete, and workflow_dispatch). Runs already started are not affected.
390+ // The state belongs to the repository and to the file path, not to a
391+ // commit or a branch, and the file does not have to exist: it can be set on
392+ // an empty repository, before the first push. Disabling a disabled workflow
393+ // succeeds and changes nothing. Needs the repo:admin scope on the
394+ // repository, and a token that is not limited to branch prefixes. Only
395+ // servers with the feature "ci.workflows" implement it (unimplemented
396+ // otherwise). Fails with not_found if the repository does not exist or the
397+ // caller cannot read it, permission_denied without the scope, and
398+ // invalid_argument if the workflow is not a file name or a path of
399+ // .github/workflows ending in .yml or .yaml.
400+ rpc DisableWorkflow(DisableWorkflowRequest) returns (DisableWorkflowResponse);
401+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
402+ // a workflow that is not disabled succeeds and changes nothing. Same access
403+ // and errors as DisableWorkflow. Events that happened while the workflow was
404+ // disabled are not replayed.
405+ rpc EnableWorkflow(EnableWorkflowRequest) returns (EnableWorkflowResponse);
374406 // ListChecks lists the checks of a commit: for each job of each run on that
375407 // sha, the one of the latest attempt, ordered by creation time then name.
376408 // A commit without a run has no check: the list is empty, which is not an
@@ -661,3 +693,65 @@ message RemoveRunnerRequest {
661693
662694 // RemoveRunnerResponse is empty.
663695 message RemoveRunnerResponse {}
696+
697+// WorkflowState tells whether a workflow file starts runs.
698+enum WorkflowState {
699+ WORKFLOW_STATE_UNSPECIFIED = 0;
700+ // The workflow starts runs for the events it declares.
701+ WORKFLOW_STATE_ENABLED = 1;
702+ // The workflow starts no run for any event (DisableWorkflow).
703+ WORKFLOW_STATE_DISABLED = 2;
704+}
705+
706+// Workflow is a workflow file of a repository and its state.
707+message Workflow {
708+ // Path is the path of the file, always ".github/workflows/<file>".
709+ string path = 1;
710+ // Name is the "name:" of the workflow at the tip of the default branch, the
711+ // path when the file has none. Empty when the default branch has no such file
712+ // or it cannot be read as a workflow.
713+ string name = 2;
714+ // State is ENABLED or DISABLED.
715+ WorkflowState state = 3;
716+}
717+
718+// ListWorkflowsRequest names the repository.
719+message ListWorkflowsRequest {
720+ // Repo is the repository name.
721+ string repo = 1;
722+}
723+
724+// ListWorkflowsResponse holds the workflows of a repository, ordered by path.
725+message ListWorkflowsResponse {
726+ repeated Workflow workflows = 1;
727+}
728+
729+// DisableWorkflowRequest names the workflow to disable.
730+message DisableWorkflowRequest {
731+ // Repo is the repository name.
732+ string repo = 1;
733+ // Workflow is the path of the file (".github/workflows/release.yml") or its
734+ // file name ("release.yml"). The file does not have to exist.
735+ string workflow = 2;
736+}
737+
738+// DisableWorkflowResponse holds the workflow after the change.
739+message DisableWorkflowResponse {
740+ // Workflow has the normalized path and state DISABLED.
741+ Workflow workflow = 1;
742+}
743+
744+// EnableWorkflowRequest names the workflow to enable.
745+message EnableWorkflowRequest {
746+ // Repo is the repository name.
747+ string repo = 1;
748+ // Workflow is the path of the file or its file name, as in
749+ // DisableWorkflowRequest.
750+ string workflow = 2;
751+}
752+
753+// EnableWorkflowResponse holds the workflow after the change.
754+message EnableWorkflowResponse {
755+ // Workflow has the normalized path and state ENABLED.
756+ Workflow workflow = 1;
757+}
@@ -282,14 +282,16 @@ message Runner {
282 // request (other fields must be unchanged).282 // request (other fields must be unchanged).
283 //283 //
284 // Servers that implement the runs side (every RPC but ListRunners,284 // Servers that implement the runs side (every RPC but ListRunners,
285-// RemoveRunner, ListAnnotations, GetStepSummaries and DispatchWorkflow) list285+// RemoveRunner, ListAnnotations, GetStepSummaries, DispatchWorkflow and the
286+// workflow state RPCs) list
286 // "ci.runs" in287 // "ci.runs" in
287 // GetVersionResponse.features; those that288 // GetVersionResponse.features; those that
288 // implement the runners side list "ci.runners", as RunnerService does. A server289 // implement the runners side list "ci.runners", as RunnerService does. A server
289 // that honours the filters of ListRunsRequest also lists "ci.runs.filters".290 // that honours the filters of ListRunsRequest also lists "ci.runs.filters".
290 // ListAnnotations needs "ci.annotations" and GetStepSummaries "ci.summaries",291 // ListAnnotations needs "ci.annotations" and GetStepSummaries "ci.summaries",
291 // which are also the features of the runner side of each. DispatchWorkflow292 // which are also the features of the runner side of each. DispatchWorkflow
292-// needs "ci.dispatch". RunJob.concurrency_group and CI_STATUS_PENDING belong293+// needs "ci.dispatch". ListWorkflows, DisableWorkflow and EnableWorkflow need
294+// "ci.workflows". RunJob.concurrency_group and CI_STATUS_PENDING belong
293 // to "ci.concurrency", Run.trusted to "ci.secrets".295 // to "ci.concurrency", Run.trusted to "ci.secrets".
294 service RunService {296 service RunService {
295 // ListRuns lists the runs of a repository, highest id (newest) first. Each297 // ListRuns lists the runs of a repository, highest id (newest) first. Each
@@ -371,6 +373,36 @@ service RunService {
371 // missing while required without default, or has a value of the wrong type373 // missing while required without default, or has a value of the wrong type
372 // or outside its choices, or if the workflow file is not valid.374 // or outside its choices, or if the workflow file is not valid.
373 rpc DispatchWorkflow(DispatchWorkflowRequest) returns (DispatchWorkflowResponse);375 rpc DispatchWorkflow(DispatchWorkflowRequest) returns (DispatchWorkflowResponse);
376+ // ListWorkflows lists the workflow files of a repository with their state,
377+ // like `gh workflow list --all`, ordered by path: the files of
378+ // .github/workflows at the tip of the default branch, and the files that are
379+ // disabled even if the default branch has no such file (a repository that
380+ // is empty, or a file not pushed yet). Needs the same access as reading the
381+ // repository. A repository without a default branch lists only its disabled
382+ // files. Only servers with the feature "ci.workflows" implement it
383+ // (unimplemented otherwise). Fails with not_found if the repository does not
384+ // exist or cannot be read.
385+ rpc ListWorkflows(ListWorkflowsRequest) returns (ListWorkflowsResponse);
386+ // DisableWorkflow disables a workflow file of a repository, like `gh
387+ // workflow disable`: no event starts a run of it any more (push,
388+ // pull_request, pull_request_target, schedule, workflow_run, create,
389+ // delete, and workflow_dispatch). Runs already started are not affected.
390+ // The state belongs to the repository and to the file path, not to a
391+ // commit or a branch, and the file does not have to exist: it can be set on
392+ // an empty repository, before the first push. Disabling a disabled workflow
393+ // succeeds and changes nothing. Needs the repo:admin scope on the
394+ // repository, and a token that is not limited to branch prefixes. Only
395+ // servers with the feature "ci.workflows" implement it (unimplemented
396+ // otherwise). Fails with not_found if the repository does not exist or the
397+ // caller cannot read it, permission_denied without the scope, and
398+ // invalid_argument if the workflow is not a file name or a path of
399+ // .github/workflows ending in .yml or .yaml.
400+ rpc DisableWorkflow(DisableWorkflowRequest) returns (DisableWorkflowResponse);
401+ // EnableWorkflow undoes DisableWorkflow, like `gh workflow enable`. Enabling
402+ // a workflow that is not disabled succeeds and changes nothing. Same access
403+ // and errors as DisableWorkflow. Events that happened while the workflow was
404+ // disabled are not replayed.
405+ rpc EnableWorkflow(EnableWorkflowRequest) returns (EnableWorkflowResponse);
374 // ListChecks lists the checks of a commit: for each job of each run on that406 // ListChecks lists the checks of a commit: for each job of each run on that
375 // sha, the one of the latest attempt, ordered by creation time then name.407 // sha, the one of the latest attempt, ordered by creation time then name.
376 // A commit without a run has no check: the list is empty, which is not an408 // A commit without a run has no check: the list is empty, which is not an
@@ -661,3 +693,65 @@ message RemoveRunnerRequest {
661 693
662 // RemoveRunnerResponse is empty.694 // RemoveRunnerResponse is empty.
663 message RemoveRunnerResponse {}695 message RemoveRunnerResponse {}
696+
697+// WorkflowState tells whether a workflow file starts runs.
698+enum WorkflowState {
699+ WORKFLOW_STATE_UNSPECIFIED = 0;
700+ // The workflow starts runs for the events it declares.
701+ WORKFLOW_STATE_ENABLED = 1;
702+ // The workflow starts no run for any event (DisableWorkflow).
703+ WORKFLOW_STATE_DISABLED = 2;
704+}
705+
706+// Workflow is a workflow file of a repository and its state.
707+message Workflow {
708+ // Path is the path of the file, always ".github/workflows/<file>".
709+ string path = 1;
710+ // Name is the "name:" of the workflow at the tip of the default branch, the
711+ // path when the file has none. Empty when the default branch has no such file
712+ // or it cannot be read as a workflow.
713+ string name = 2;
714+ // State is ENABLED or DISABLED.
715+ WorkflowState state = 3;
716+}
717+
718+// ListWorkflowsRequest names the repository.
719+message ListWorkflowsRequest {
720+ // Repo is the repository name.
721+ string repo = 1;
722+}
723+
724+// ListWorkflowsResponse holds the workflows of a repository, ordered by path.
725+message ListWorkflowsResponse {
726+ repeated Workflow workflows = 1;
727+}
728+
729+// DisableWorkflowRequest names the workflow to disable.
730+message DisableWorkflowRequest {
731+ // Repo is the repository name.
732+ string repo = 1;
733+ // Workflow is the path of the file (".github/workflows/release.yml") or its
734+ // file name ("release.yml"). The file does not have to exist.
735+ string workflow = 2;
736+}
737+
738+// DisableWorkflowResponse holds the workflow after the change.
739+message DisableWorkflowResponse {
740+ // Workflow has the normalized path and state DISABLED.
741+ Workflow workflow = 1;
742+}
743+
744+// EnableWorkflowRequest names the workflow to enable.
745+message EnableWorkflowRequest {
746+ // Repo is the repository name.
747+ string repo = 1;
748+ // Workflow is the path of the file or its file name, as in
749+ // DisableWorkflowRequest.
750+ string workflow = 2;
751+}
752+
753+// EnableWorkflowResponse holds the workflow after the change.
754+message EnableWorkflowResponse {
755+ // Workflow has the normalized path and state ENABLED.
756+ Workflow workflow = 1;
757+}

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