detached aa5a961

feat(execstream): carry a job and its steps, logs and result on the frame stream

commit
aa5a961e600d37aa253759cc2e3fc0b901055600
author
brice · · 2026-10-12 18:20 UTC
parent
e0de5de
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +871 -48
M internal/execstream/exec.proto +79 -2
@@ -21,6 +21,10 @@ message Frame {
2121 Output output = 3; // executor -> host
2222 Exit exit = 4; // executor -> host, last frame
2323 Cancel cancel = 5; // host -> executor
24+ StepStarted step_started = 6; // executor -> host
25+ StepCompleted step_completed = 7; // executor -> host
26+ Log log = 8; // executor -> host
27+ JobResult job_result = 9; // executor -> host, before Exit
2428 }
2529 }
2630
@@ -30,11 +34,32 @@ message Hello {
3034 string hostname = 3;
3135 }
3236
33-// Run asks the executor to run a script. CI-1 stub executor: the script is run
34-// with `sh -c`; an empty script means the built-in diagnostics script.
37+// Run asks the executor to run a script or a job. Without job it is the stub
38+// executor of sandbox-check: the script is run with `sh -c`; an empty script
39+// means the built-in diagnostics script. With job the executor runs the job
40+// like a GitHub Actions runner and reports it with StepStarted, Log,
41+// StepCompleted and JobResult frames, then Exit.
3542 message Run {
3643 string script = 1;
3744 map<string, string> env = 2;
45+ JobSpec job = 3;
46+}
47+
48+// JobSpec is the job to run: the fields of the job that ermite handed to the
49+// host process, minus the tokens (the sandbox never sees them).
50+message JobSpec {
51+ string workflow_path = 1;
52+ string workflow_yaml = 2;
53+ string job_key = 3;
54+ string matrix = 4; // JSON object
55+ string contexts = 5; // JSON object
56+ string repo = 6; // repository name
57+ int32 timeout_minutes = 7;
58+ string runner_name = 8;
59+ string runner_version = 9;
60+ // runner_environment is the runner.environment value ("self-hosted").
61+ string runner_environment = 10;
62+ bool debug = 11; // ACTIONS_STEP_DEBUG
3863 }
3964
4065 enum StreamKind {
@@ -61,4 +86,56 @@ message Cancel {
6186 // reason is for the job log and diagnostics ("cancelled by the user",
6287 // "job timeout", ...); it never changes the behaviour.
6388 string reason = 1;
89+ // kind tells the job executor how the job ends: cancelled, timed out, or
90+ // cancelled because the runner shuts down. The stub executor ignores it.
91+ CancelKind kind = 2;
92+}
93+
94+enum CancelKind {
95+ CANCEL_KIND_UNSPECIFIED = 0; // same as USER
96+ CANCEL_KIND_USER = 1;
97+ CANCEL_KIND_TIMEOUT = 2;
98+ CANCEL_KIND_SHUTDOWN = 3;
99+}
100+
101+// Steps are numbered like GitHub's log: "Set up job" is 1, the steps of the
102+// workflow follow, "Complete job" is last.
103+message StepStarted {
104+ int32 number = 1;
105+ string name = 2;
106+ int64 unix_nano = 3;
107+}
108+
109+message StepCompleted {
110+ int32 number = 1;
111+ string name = 2;
112+ StepConclusion conclusion = 3;
113+ int64 unix_nano = 4;
114+}
115+
116+enum StepConclusion {
117+ STEP_CONCLUSION_UNSPECIFIED = 0;
118+ STEP_CONCLUSION_SUCCESS = 1;
119+ STEP_CONCLUSION_FAILURE = 2;
120+ STEP_CONCLUSION_CANCELLED = 3;
121+ STEP_CONCLUSION_SKIPPED = 4;
122+ STEP_CONCLUSION_TIMED_OUT = 5; // jobs only
123+}
124+
125+// Log is one line of the job log, without terminator, valid UTF-8. A line of
126+// more than 64 KiB is cut by the executor into pieces at character boundaries;
127+// every piece but the first has continued set (the AppendLog rule).
128+message Log {
129+ int32 step = 1;
130+ int64 unix_nano = 2;
131+ string text = 3;
132+ bool continued = 4;
133+}
134+
135+// JobResult is the outcome of the job; Exit follows.
136+message JobResult {
137+ StepConclusion conclusion = 1;
138+ map<string, string> outputs = 2;
139+ string error = 3;
140+ bool runner_shutdown = 4;
64141 }
@@ -21,6 +21,10 @@ message Frame {
21 Output output = 3; // executor -> host21 Output output = 3; // executor -> host
22 Exit exit = 4; // executor -> host, last frame22 Exit exit = 4; // executor -> host, last frame
23 Cancel cancel = 5; // host -> executor23 Cancel cancel = 5; // host -> executor
24+ StepStarted step_started = 6; // executor -> host
25+ StepCompleted step_completed = 7; // executor -> host
26+ Log log = 8; // executor -> host
27+ JobResult job_result = 9; // executor -> host, before Exit
24 }28 }
25 }29 }
26 30
@@ -30,11 +34,32 @@ message Hello {
30 string hostname = 3;34 string hostname = 3;
31 }35 }
32 36
33-// Run asks the executor to run a script. CI-1 stub executor: the script is run37+// Run asks the executor to run a script or a job. Without job it is the stub
34-// with `sh -c`; an empty script means the built-in diagnostics script.38+// executor of sandbox-check: the script is run with `sh -c`; an empty script
39+// means the built-in diagnostics script. With job the executor runs the job
40+// like a GitHub Actions runner and reports it with StepStarted, Log,
41+// StepCompleted and JobResult frames, then Exit.
35 message Run {42 message Run {
36 string script = 1;43 string script = 1;
37 map<string, string> env = 2;44 map<string, string> env = 2;
45+ JobSpec job = 3;
46+}
47+
48+// JobSpec is the job to run: the fields of the job that ermite handed to the
49+// host process, minus the tokens (the sandbox never sees them).
50+message JobSpec {
51+ string workflow_path = 1;
52+ string workflow_yaml = 2;
53+ string job_key = 3;
54+ string matrix = 4; // JSON object
55+ string contexts = 5; // JSON object
56+ string repo = 6; // repository name
57+ int32 timeout_minutes = 7;
58+ string runner_name = 8;
59+ string runner_version = 9;
60+ // runner_environment is the runner.environment value ("self-hosted").
61+ string runner_environment = 10;
62+ bool debug = 11; // ACTIONS_STEP_DEBUG
38 }63 }
39 64
40 enum StreamKind {65 enum StreamKind {
@@ -61,4 +86,56 @@ message Cancel {
61 // reason is for the job log and diagnostics ("cancelled by the user",86 // reason is for the job log and diagnostics ("cancelled by the user",
62 // "job timeout", ...); it never changes the behaviour.87 // "job timeout", ...); it never changes the behaviour.
63 string reason = 1;88 string reason = 1;
89+ // kind tells the job executor how the job ends: cancelled, timed out, or
90+ // cancelled because the runner shuts down. The stub executor ignores it.
91+ CancelKind kind = 2;
92+}
93+
94+enum CancelKind {
95+ CANCEL_KIND_UNSPECIFIED = 0; // same as USER
96+ CANCEL_KIND_USER = 1;
97+ CANCEL_KIND_TIMEOUT = 2;
98+ CANCEL_KIND_SHUTDOWN = 3;
99+}
100+
101+// Steps are numbered like GitHub's log: "Set up job" is 1, the steps of the
102+// workflow follow, "Complete job" is last.
103+message StepStarted {
104+ int32 number = 1;
105+ string name = 2;
106+ int64 unix_nano = 3;
107+}
108+
109+message StepCompleted {
110+ int32 number = 1;
111+ string name = 2;
112+ StepConclusion conclusion = 3;
113+ int64 unix_nano = 4;
114+}
115+
116+enum StepConclusion {
117+ STEP_CONCLUSION_UNSPECIFIED = 0;
118+ STEP_CONCLUSION_SUCCESS = 1;
119+ STEP_CONCLUSION_FAILURE = 2;
120+ STEP_CONCLUSION_CANCELLED = 3;
121+ STEP_CONCLUSION_SKIPPED = 4;
122+ STEP_CONCLUSION_TIMED_OUT = 5; // jobs only
123+}
124+
125+// Log is one line of the job log, without terminator, valid UTF-8. A line of
126+// more than 64 KiB is cut by the executor into pieces at character boundaries;
127+// every piece but the first has continued set (the AppendLog rule).
128+message Log {
129+ int32 step = 1;
130+ int64 unix_nano = 2;
131+ string text = 3;
132+ bool continued = 4;
133+}
134+
135+// JobResult is the outcome of the job; Exit follows.
136+message JobResult {
137+ StepConclusion conclusion = 1;
138+ map<string, string> outputs = 2;
139+ string error = 3;
140+ bool runner_shutdown = 4;
64 }141 }
M internal/execstream/execpb/exec.pb.go +717 -44
@@ -79,6 +79,116 @@ func (StreamKind) EnumDescriptor() ([]byte, []int) {
7979 return file_exec_proto_rawDescGZIP(), []int{0}
8080 }
8181
82+type CancelKind int32
83+
84+const (
85+ CancelKind_CANCEL_KIND_UNSPECIFIED CancelKind = 0 // same as USER
86+ CancelKind_CANCEL_KIND_USER CancelKind = 1
87+ CancelKind_CANCEL_KIND_TIMEOUT CancelKind = 2
88+ CancelKind_CANCEL_KIND_SHUTDOWN CancelKind = 3
89+)
90+
91+// Enum value maps for CancelKind.
92+var (
93+ CancelKind_name = map[int32]string{
94+ 0: "CANCEL_KIND_UNSPECIFIED",
95+ 1: "CANCEL_KIND_USER",
96+ 2: "CANCEL_KIND_TIMEOUT",
97+ 3: "CANCEL_KIND_SHUTDOWN",
98+ }
99+ CancelKind_value = map[string]int32{
100+ "CANCEL_KIND_UNSPECIFIED": 0,
101+ "CANCEL_KIND_USER": 1,
102+ "CANCEL_KIND_TIMEOUT": 2,
103+ "CANCEL_KIND_SHUTDOWN": 3,
104+ }
105+)
106+
107+func (x CancelKind) Enum() *CancelKind {
108+ p := new(CancelKind)
109+ *p = x
110+ return p
111+}
112+
113+func (x CancelKind) String() string {
114+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
115+}
116+
117+func (CancelKind) Descriptor() protoreflect.EnumDescriptor {
118+ return file_exec_proto_enumTypes[1].Descriptor()
119+}
120+
121+func (CancelKind) Type() protoreflect.EnumType {
122+ return &file_exec_proto_enumTypes[1]
123+}
124+
125+func (x CancelKind) Number() protoreflect.EnumNumber {
126+ return protoreflect.EnumNumber(x)
127+}
128+
129+// Deprecated: Use CancelKind.Descriptor instead.
130+func (CancelKind) EnumDescriptor() ([]byte, []int) {
131+ return file_exec_proto_rawDescGZIP(), []int{1}
132+}
133+
134+type StepConclusion int32
135+
136+const (
137+ StepConclusion_STEP_CONCLUSION_UNSPECIFIED StepConclusion = 0
138+ StepConclusion_STEP_CONCLUSION_SUCCESS StepConclusion = 1
139+ StepConclusion_STEP_CONCLUSION_FAILURE StepConclusion = 2
140+ StepConclusion_STEP_CONCLUSION_CANCELLED StepConclusion = 3
141+ StepConclusion_STEP_CONCLUSION_SKIPPED StepConclusion = 4
142+ StepConclusion_STEP_CONCLUSION_TIMED_OUT StepConclusion = 5 // jobs only
143+)
144+
145+// Enum value maps for StepConclusion.
146+var (
147+ StepConclusion_name = map[int32]string{
148+ 0: "STEP_CONCLUSION_UNSPECIFIED",
149+ 1: "STEP_CONCLUSION_SUCCESS",
150+ 2: "STEP_CONCLUSION_FAILURE",
151+ 3: "STEP_CONCLUSION_CANCELLED",
152+ 4: "STEP_CONCLUSION_SKIPPED",
153+ 5: "STEP_CONCLUSION_TIMED_OUT",
154+ }
155+ StepConclusion_value = map[string]int32{
156+ "STEP_CONCLUSION_UNSPECIFIED": 0,
157+ "STEP_CONCLUSION_SUCCESS": 1,
158+ "STEP_CONCLUSION_FAILURE": 2,
159+ "STEP_CONCLUSION_CANCELLED": 3,
160+ "STEP_CONCLUSION_SKIPPED": 4,
161+ "STEP_CONCLUSION_TIMED_OUT": 5,
162+ }
163+)
164+
165+func (x StepConclusion) Enum() *StepConclusion {
166+ p := new(StepConclusion)
167+ *p = x
168+ return p
169+}
170+
171+func (x StepConclusion) String() string {
172+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
173+}
174+
175+func (StepConclusion) Descriptor() protoreflect.EnumDescriptor {
176+ return file_exec_proto_enumTypes[2].Descriptor()
177+}
178+
179+func (StepConclusion) Type() protoreflect.EnumType {
180+ return &file_exec_proto_enumTypes[2]
181+}
182+
183+func (x StepConclusion) Number() protoreflect.EnumNumber {
184+ return protoreflect.EnumNumber(x)
185+}
186+
187+// Deprecated: Use StepConclusion.Descriptor instead.
188+func (StepConclusion) EnumDescriptor() ([]byte, []int) {
189+ return file_exec_proto_rawDescGZIP(), []int{2}
190+}
191+
82192 // Frame is the only message on the stream.
83193 type Frame struct {
84194 state protoimpl.MessageState `protogen:"open.v1"`
@@ -89,6 +199,10 @@ type Frame struct {
89199 // *Frame_Output
90200 // *Frame_Exit
91201 // *Frame_Cancel
202+ // *Frame_StepStarted
203+ // *Frame_StepCompleted
204+ // *Frame_Log
205+ // *Frame_JobResult
92206 Body isFrame_Body `protobuf_oneof:"body"`
93207 unknownFields protoimpl.UnknownFields
94208 sizeCache protoimpl.SizeCache
@@ -176,6 +290,42 @@ func (x *Frame) GetCancel() *Cancel {
176290 return nil
177291 }
178292
293+func (x *Frame) GetStepStarted() *StepStarted {
294+ if x != nil {
295+ if x, ok := x.Body.(*Frame_StepStarted); ok {
296+ return x.StepStarted
297+ }
298+ }
299+ return nil
300+}
301+
302+func (x *Frame) GetStepCompleted() *StepCompleted {
303+ if x != nil {
304+ if x, ok := x.Body.(*Frame_StepCompleted); ok {
305+ return x.StepCompleted
306+ }
307+ }
308+ return nil
309+}
310+
311+func (x *Frame) GetLog() *Log {
312+ if x != nil {
313+ if x, ok := x.Body.(*Frame_Log); ok {
314+ return x.Log
315+ }
316+ }
317+ return nil
318+}
319+
320+func (x *Frame) GetJobResult() *JobResult {
321+ if x != nil {
322+ if x, ok := x.Body.(*Frame_JobResult); ok {
323+ return x.JobResult
324+ }
325+ }
326+ return nil
327+}
328+
179329 type isFrame_Body interface {
180330 isFrame_Body()
181331 }
@@ -200,6 +350,22 @@ type Frame_Cancel struct {
200350 Cancel *Cancel `protobuf:"bytes,5,opt,name=cancel,proto3,oneof"` // host -> executor
201351 }
202352
353+type Frame_StepStarted struct {
354+ StepStarted *StepStarted `protobuf:"bytes,6,opt,name=step_started,json=stepStarted,proto3,oneof"` // executor -> host
355+}
356+
357+type Frame_StepCompleted struct {
358+ StepCompleted *StepCompleted `protobuf:"bytes,7,opt,name=step_completed,json=stepCompleted,proto3,oneof"` // executor -> host
359+}
360+
361+type Frame_Log struct {
362+ Log *Log `protobuf:"bytes,8,opt,name=log,proto3,oneof"` // executor -> host
363+}
364+
365+type Frame_JobResult struct {
366+ JobResult *JobResult `protobuf:"bytes,9,opt,name=job_result,json=jobResult,proto3,oneof"` // executor -> host, before Exit
367+}
368+
203369 func (*Frame_Hello) isFrame_Body() {}
204370
205371 func (*Frame_Run) isFrame_Body() {}
@@ -210,6 +376,14 @@ func (*Frame_Exit) isFrame_Body() {}
210376
211377 func (*Frame_Cancel) isFrame_Body() {}
212378
379+func (*Frame_StepStarted) isFrame_Body() {}
380+
381+func (*Frame_StepCompleted) isFrame_Body() {}
382+
383+func (*Frame_Log) isFrame_Body() {}
384+
385+func (*Frame_JobResult) isFrame_Body() {}
386+
213387 type Hello struct {
214388 state protoimpl.MessageState `protogen:"open.v1"`
215389 Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor
@@ -270,12 +444,16 @@ func (x *Hello) GetHostname() string {
270444 return ""
271445 }
272446
273-// Run asks the executor to run a script. CI-1 stub executor: the script is run
274-// with `sh -c`; an empty script means the built-in diagnostics script.
447+// Run asks the executor to run a script or a job. Without job it is the stub
448+// executor of sandbox-check: the script is run with `sh -c`; an empty script
449+// means the built-in diagnostics script. With job the executor runs the job
450+// like a GitHub Actions runner and reports it with StepStarted, Log,
451+// StepCompleted and JobResult frames, then Exit.
275452 type Run struct {
276453 state protoimpl.MessageState `protogen:"open.v1"`
277454 Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
278455 Env map[string]string `protobuf:"bytes,2,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
456+ Job *JobSpec `protobuf:"bytes,3,opt,name=job,proto3" json:"job,omitempty"`
279457 unknownFields protoimpl.UnknownFields
280458 sizeCache protoimpl.SizeCache
281459 }
@@ -324,6 +502,140 @@ func (x *Run) GetEnv() map[string]string {
324502 return nil
325503 }
326504
505+func (x *Run) GetJob() *JobSpec {
506+ if x != nil {
507+ return x.Job
508+ }
509+ return nil
510+}
511+
512+// JobSpec is the job to run: the fields of the job that ermite handed to the
513+// host process, minus the tokens (the sandbox never sees them).
514+type JobSpec struct {
515+ state protoimpl.MessageState `protogen:"open.v1"`
516+ WorkflowPath string `protobuf:"bytes,1,opt,name=workflow_path,json=workflowPath,proto3" json:"workflow_path,omitempty"`
517+ WorkflowYaml string `protobuf:"bytes,2,opt,name=workflow_yaml,json=workflowYaml,proto3" json:"workflow_yaml,omitempty"`
518+ JobKey string `protobuf:"bytes,3,opt,name=job_key,json=jobKey,proto3" json:"job_key,omitempty"`
519+ Matrix string `protobuf:"bytes,4,opt,name=matrix,proto3" json:"matrix,omitempty"` // JSON object
520+ Contexts string `protobuf:"bytes,5,opt,name=contexts,proto3" json:"contexts,omitempty"` // JSON object
521+ Repo string `protobuf:"bytes,6,opt,name=repo,proto3" json:"repo,omitempty"` // repository name
522+ TimeoutMinutes int32 `protobuf:"varint,7,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"`
523+ RunnerName string `protobuf:"bytes,8,opt,name=runner_name,json=runnerName,proto3" json:"runner_name,omitempty"`
524+ RunnerVersion string `protobuf:"bytes,9,opt,name=runner_version,json=runnerVersion,proto3" json:"runner_version,omitempty"`
525+ // runner_environment is the runner.environment value ("self-hosted").
526+ RunnerEnvironment string `protobuf:"bytes,10,opt,name=runner_environment,json=runnerEnvironment,proto3" json:"runner_environment,omitempty"`
527+ Debug bool `protobuf:"varint,11,opt,name=debug,proto3" json:"debug,omitempty"` // ACTIONS_STEP_DEBUG
528+ unknownFields protoimpl.UnknownFields
529+ sizeCache protoimpl.SizeCache
530+}
531+
532+func (x *JobSpec) Reset() {
533+ *x = JobSpec{}
534+ mi := &file_exec_proto_msgTypes[3]
535+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
536+ ms.StoreMessageInfo(mi)
537+}
538+
539+func (x *JobSpec) String() string {
540+ return protoimpl.X.MessageStringOf(x)
541+}
542+
543+func (*JobSpec) ProtoMessage() {}
544+
545+func (x *JobSpec) ProtoReflect() protoreflect.Message {
546+ mi := &file_exec_proto_msgTypes[3]
547+ if x != nil {
548+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
549+ if ms.LoadMessageInfo() == nil {
550+ ms.StoreMessageInfo(mi)
551+ }
552+ return ms
553+ }
554+ return mi.MessageOf(x)
555+}
556+
557+// Deprecated: Use JobSpec.ProtoReflect.Descriptor instead.
558+func (*JobSpec) Descriptor() ([]byte, []int) {
559+ return file_exec_proto_rawDescGZIP(), []int{3}
560+}
561+
562+func (x *JobSpec) GetWorkflowPath() string {
563+ if x != nil {
564+ return x.WorkflowPath
565+ }
566+ return ""
567+}
568+
569+func (x *JobSpec) GetWorkflowYaml() string {
570+ if x != nil {
571+ return x.WorkflowYaml
572+ }
573+ return ""
574+}
575+
576+func (x *JobSpec) GetJobKey() string {
577+ if x != nil {
578+ return x.JobKey
579+ }
580+ return ""
581+}
582+
583+func (x *JobSpec) GetMatrix() string {
584+ if x != nil {
585+ return x.Matrix
586+ }
587+ return ""
588+}
589+
590+func (x *JobSpec) GetContexts() string {
591+ if x != nil {
592+ return x.Contexts
593+ }
594+ return ""
595+}
596+
597+func (x *JobSpec) GetRepo() string {
598+ if x != nil {
599+ return x.Repo
600+ }
601+ return ""
602+}
603+
604+func (x *JobSpec) GetTimeoutMinutes() int32 {
605+ if x != nil {
606+ return x.TimeoutMinutes
607+ }
608+ return 0
609+}
610+
611+func (x *JobSpec) GetRunnerName() string {
612+ if x != nil {
613+ return x.RunnerName
614+ }
615+ return ""
616+}
617+
618+func (x *JobSpec) GetRunnerVersion() string {
619+ if x != nil {
620+ return x.RunnerVersion
621+ }
622+ return ""
623+}
624+
625+func (x *JobSpec) GetRunnerEnvironment() string {
626+ if x != nil {
627+ return x.RunnerEnvironment
628+ }
629+ return ""
630+}
631+
632+func (x *JobSpec) GetDebug() bool {
633+ if x != nil {
634+ return x.Debug
635+ }
636+ return false
637+}
638+
327639 type Output struct {
328640 state protoimpl.MessageState `protogen:"open.v1"`
329641 Stream StreamKind `protobuf:"varint,1,opt,name=stream,proto3,enum=ermite.runner.exec.v1.StreamKind" json:"stream,omitempty"`
@@ -335,7 +647,7 @@ type Output struct {
335647
336648 func (x *Output) Reset() {
337649 *x = Output{}
338- mi := &file_exec_proto_msgTypes[3]
650+ mi := &file_exec_proto_msgTypes[4]
339651 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
340652 ms.StoreMessageInfo(mi)
341653 }
@@ -347,7 +659,7 @@ func (x *Output) String() string {
347659 func (*Output) ProtoMessage() {}
348660
349661 func (x *Output) ProtoReflect() protoreflect.Message {
350- mi := &file_exec_proto_msgTypes[3]
662+ mi := &file_exec_proto_msgTypes[4]
351663 if x != nil {
352664 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
353665 if ms.LoadMessageInfo() == nil {
@@ -360,7 +672,7 @@ func (x *Output) ProtoReflect() protoreflect.Message {
360672
361673 // Deprecated: Use Output.ProtoReflect.Descriptor instead.
362674 func (*Output) Descriptor() ([]byte, []int) {
363- return file_exec_proto_rawDescGZIP(), []int{3}
675+ return file_exec_proto_rawDescGZIP(), []int{4}
364676 }
365677
366678 func (x *Output) GetStream() StreamKind {
@@ -394,7 +706,7 @@ type Exit struct {
394706
395707 func (x *Exit) Reset() {
396708 *x = Exit{}
397- mi := &file_exec_proto_msgTypes[4]
709+ mi := &file_exec_proto_msgTypes[5]
398710 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
399711 ms.StoreMessageInfo(mi)
400712 }
@@ -406,7 +718,7 @@ func (x *Exit) String() string {
406718 func (*Exit) ProtoMessage() {}
407719
408720 func (x *Exit) ProtoReflect() protoreflect.Message {
409- mi := &file_exec_proto_msgTypes[4]
721+ mi := &file_exec_proto_msgTypes[5]
410722 if x != nil {
411723 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
412724 if ms.LoadMessageInfo() == nil {
@@ -419,7 +731,7 @@ func (x *Exit) ProtoReflect() protoreflect.Message {
419731
420732 // Deprecated: Use Exit.ProtoReflect.Descriptor instead.
421733 func (*Exit) Descriptor() ([]byte, []int) {
422- return file_exec_proto_rawDescGZIP(), []int{4}
734+ return file_exec_proto_rawDescGZIP(), []int{5}
423735 }
424736
425737 func (x *Exit) GetCode() int32 {
@@ -443,14 +755,17 @@ type Cancel struct {
443755 state protoimpl.MessageState `protogen:"open.v1"`
444756 // reason is for the job log and diagnostics ("cancelled by the user",
445757 // "job timeout", ...); it never changes the behaviour.
446- Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"`
758+ Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"`
759+ // kind tells the job executor how the job ends: cancelled, timed out, or
760+ // cancelled because the runner shuts down. The stub executor ignores it.
761+ Kind CancelKind `protobuf:"varint,2,opt,name=kind,proto3,enum=ermite.runner.exec.v1.CancelKind" json:"kind,omitempty"`
447762 unknownFields protoimpl.UnknownFields
448763 sizeCache protoimpl.SizeCache
449764 }
450765
451766 func (x *Cancel) Reset() {
452767 *x = Cancel{}
453- mi := &file_exec_proto_msgTypes[5]
768+ mi := &file_exec_proto_msgTypes[6]
454769 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
455770 ms.StoreMessageInfo(mi)
456771 }
@@ -462,7 +777,7 @@ func (x *Cancel) String() string {
462777 func (*Cancel) ProtoMessage() {}
463778
464779 func (x *Cancel) ProtoReflect() protoreflect.Message {
465- mi := &file_exec_proto_msgTypes[5]
780+ mi := &file_exec_proto_msgTypes[6]
466781 if x != nil {
467782 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
468783 if ms.LoadMessageInfo() == nil {
@@ -475,7 +790,7 @@ func (x *Cancel) ProtoReflect() protoreflect.Message {
475790
476791 // Deprecated: Use Cancel.ProtoReflect.Descriptor instead.
477792 func (*Cancel) Descriptor() ([]byte, []int) {
478- return file_exec_proto_rawDescGZIP(), []int{5}
793+ return file_exec_proto_rawDescGZIP(), []int{6}
479794 }
480795
481796 func (x *Cancel) GetReason() string {
@@ -485,43 +800,380 @@ func (x *Cancel) GetReason() string {
485800 return ""
486801 }
487802
803+func (x *Cancel) GetKind() CancelKind {
804+ if x != nil {
805+ return x.Kind
806+ }
807+ return CancelKind_CANCEL_KIND_UNSPECIFIED
808+}
809+
810+// Steps are numbered like GitHub's log: "Set up job" is 1, the steps of the
811+// workflow follow, "Complete job" is last.
812+type StepStarted struct {
813+ state protoimpl.MessageState `protogen:"open.v1"`
814+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
815+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
816+ UnixNano int64 `protobuf:"varint,3,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
817+ unknownFields protoimpl.UnknownFields
818+ sizeCache protoimpl.SizeCache
819+}
820+
821+func (x *StepStarted) Reset() {
822+ *x = StepStarted{}
823+ mi := &file_exec_proto_msgTypes[7]
824+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
825+ ms.StoreMessageInfo(mi)
826+}
827+
828+func (x *StepStarted) String() string {
829+ return protoimpl.X.MessageStringOf(x)
830+}
831+
832+func (*StepStarted) ProtoMessage() {}
833+
834+func (x *StepStarted) ProtoReflect() protoreflect.Message {
835+ mi := &file_exec_proto_msgTypes[7]
836+ if x != nil {
837+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
838+ if ms.LoadMessageInfo() == nil {
839+ ms.StoreMessageInfo(mi)
840+ }
841+ return ms
842+ }
843+ return mi.MessageOf(x)
844+}
845+
846+// Deprecated: Use StepStarted.ProtoReflect.Descriptor instead.
847+func (*StepStarted) Descriptor() ([]byte, []int) {
848+ return file_exec_proto_rawDescGZIP(), []int{7}
849+}
850+
851+func (x *StepStarted) GetNumber() int32 {
852+ if x != nil {
853+ return x.Number
854+ }
855+ return 0
856+}
857+
858+func (x *StepStarted) GetName() string {
859+ if x != nil {
860+ return x.Name
861+ }
862+ return ""
863+}
864+
865+func (x *StepStarted) GetUnixNano() int64 {
866+ if x != nil {
867+ return x.UnixNano
868+ }
869+ return 0
870+}
871+
872+type StepCompleted struct {
873+ state protoimpl.MessageState `protogen:"open.v1"`
874+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
875+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
876+ Conclusion StepConclusion `protobuf:"varint,3,opt,name=conclusion,proto3,enum=ermite.runner.exec.v1.StepConclusion" json:"conclusion,omitempty"`
877+ UnixNano int64 `protobuf:"varint,4,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
878+ unknownFields protoimpl.UnknownFields
879+ sizeCache protoimpl.SizeCache
880+}
881+
882+func (x *StepCompleted) Reset() {
883+ *x = StepCompleted{}
884+ mi := &file_exec_proto_msgTypes[8]
885+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
886+ ms.StoreMessageInfo(mi)
887+}
888+
889+func (x *StepCompleted) String() string {
890+ return protoimpl.X.MessageStringOf(x)
891+}
892+
893+func (*StepCompleted) ProtoMessage() {}
894+
895+func (x *StepCompleted) ProtoReflect() protoreflect.Message {
896+ mi := &file_exec_proto_msgTypes[8]
897+ if x != nil {
898+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
899+ if ms.LoadMessageInfo() == nil {
900+ ms.StoreMessageInfo(mi)
901+ }
902+ return ms
903+ }
904+ return mi.MessageOf(x)
905+}
906+
907+// Deprecated: Use StepCompleted.ProtoReflect.Descriptor instead.
908+func (*StepCompleted) Descriptor() ([]byte, []int) {
909+ return file_exec_proto_rawDescGZIP(), []int{8}
910+}
911+
912+func (x *StepCompleted) GetNumber() int32 {
913+ if x != nil {
914+ return x.Number
915+ }
916+ return 0
917+}
918+
919+func (x *StepCompleted) GetName() string {
920+ if x != nil {
921+ return x.Name
922+ }
923+ return ""
924+}
925+
926+func (x *StepCompleted) GetConclusion() StepConclusion {
927+ if x != nil {
928+ return x.Conclusion
929+ }
930+ return StepConclusion_STEP_CONCLUSION_UNSPECIFIED
931+}
932+
933+func (x *StepCompleted) GetUnixNano() int64 {
934+ if x != nil {
935+ return x.UnixNano
936+ }
937+ return 0
938+}
939+
940+// Log is one line of the job log, without terminator, valid UTF-8. A line of
941+// more than 64 KiB is cut by the executor into pieces at character boundaries;
942+// every piece but the first has continued set (the AppendLog rule).
943+type Log struct {
944+ state protoimpl.MessageState `protogen:"open.v1"`
945+ Step int32 `protobuf:"varint,1,opt,name=step,proto3" json:"step,omitempty"`
946+ UnixNano int64 `protobuf:"varint,2,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
947+ Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"`
948+ Continued bool `protobuf:"varint,4,opt,name=continued,proto3" json:"continued,omitempty"`
949+ unknownFields protoimpl.UnknownFields
950+ sizeCache protoimpl.SizeCache
951+}
952+
953+func (x *Log) Reset() {
954+ *x = Log{}
955+ mi := &file_exec_proto_msgTypes[9]
956+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
957+ ms.StoreMessageInfo(mi)
958+}
959+
960+func (x *Log) String() string {
961+ return protoimpl.X.MessageStringOf(x)
962+}
963+
964+func (*Log) ProtoMessage() {}
965+
966+func (x *Log) ProtoReflect() protoreflect.Message {
967+ mi := &file_exec_proto_msgTypes[9]
968+ if x != nil {
969+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
970+ if ms.LoadMessageInfo() == nil {
971+ ms.StoreMessageInfo(mi)
972+ }
973+ return ms
974+ }
975+ return mi.MessageOf(x)
976+}
977+
978+// Deprecated: Use Log.ProtoReflect.Descriptor instead.
979+func (*Log) Descriptor() ([]byte, []int) {
980+ return file_exec_proto_rawDescGZIP(), []int{9}
981+}
982+
983+func (x *Log) GetStep() int32 {
984+ if x != nil {
985+ return x.Step
986+ }
987+ return 0
988+}
989+
990+func (x *Log) GetUnixNano() int64 {
991+ if x != nil {
992+ return x.UnixNano
993+ }
994+ return 0
995+}
996+
997+func (x *Log) GetText() string {
998+ if x != nil {
999+ return x.Text
1000+ }
1001+ return ""
1002+}
1003+
1004+func (x *Log) GetContinued() bool {
1005+ if x != nil {
1006+ return x.Continued
1007+ }
1008+ return false
1009+}
1010+
1011+// JobResult is the outcome of the job; Exit follows.
1012+type JobResult struct {
1013+ state protoimpl.MessageState `protogen:"open.v1"`
1014+ Conclusion StepConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.runner.exec.v1.StepConclusion" json:"conclusion,omitempty"`
1015+ Outputs map[string]string `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
1016+ Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
1017+ RunnerShutdown bool `protobuf:"varint,4,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"`
1018+ unknownFields protoimpl.UnknownFields
1019+ sizeCache protoimpl.SizeCache
1020+}
1021+
1022+func (x *JobResult) Reset() {
1023+ *x = JobResult{}
1024+ mi := &file_exec_proto_msgTypes[10]
1025+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1026+ ms.StoreMessageInfo(mi)
1027+}
1028+
1029+func (x *JobResult) String() string {
1030+ return protoimpl.X.MessageStringOf(x)
1031+}
1032+
1033+func (*JobResult) ProtoMessage() {}
1034+
1035+func (x *JobResult) ProtoReflect() protoreflect.Message {
1036+ mi := &file_exec_proto_msgTypes[10]
1037+ if x != nil {
1038+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1039+ if ms.LoadMessageInfo() == nil {
1040+ ms.StoreMessageInfo(mi)
1041+ }
1042+ return ms
1043+ }
1044+ return mi.MessageOf(x)
1045+}
1046+
1047+// Deprecated: Use JobResult.ProtoReflect.Descriptor instead.
1048+func (*JobResult) Descriptor() ([]byte, []int) {
1049+ return file_exec_proto_rawDescGZIP(), []int{10}
1050+}
1051+
1052+func (x *JobResult) GetConclusion() StepConclusion {
1053+ if x != nil {
1054+ return x.Conclusion
1055+ }
1056+ return StepConclusion_STEP_CONCLUSION_UNSPECIFIED
1057+}
1058+
1059+func (x *JobResult) GetOutputs() map[string]string {
1060+ if x != nil {
1061+ return x.Outputs
1062+ }
1063+ return nil
1064+}
1065+
1066+func (x *JobResult) GetError() string {
1067+ if x != nil {
1068+ return x.Error
1069+ }
1070+ return ""
1071+}
1072+
1073+func (x *JobResult) GetRunnerShutdown() bool {
1074+ if x != nil {
1075+ return x.RunnerShutdown
1076+ }
1077+ return false
1078+}
1079+
4881080 var File_exec_proto protoreflect.FileDescriptor
4891081
4901082 const file_exec_proto_rawDesc = "" +
4911083 "\n" +
4921084 "\n" +
493- "exec.proto\x12\x15ermite.runner.exec.v1\"\x9a\x02\n" +
1085+ "exec.proto\x12\x15ermite.runner.exec.v1\"\xa5\x04\n" +
4941086 "\x05Frame\x124\n" +
4951087 "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" +
4961088 "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" +
4971089 "\x06output\x18\x03 \x01(\v2\x1d.ermite.runner.exec.v1.OutputH\x00R\x06output\x121\n" +
4981090 "\x04exit\x18\x04 \x01(\v2\x1b.ermite.runner.exec.v1.ExitH\x00R\x04exit\x127\n" +
499- "\x06cancel\x18\x05 \x01(\v2\x1d.ermite.runner.exec.v1.CancelH\x00R\x06cancelB\x06\n" +
1091+ "\x06cancel\x18\x05 \x01(\v2\x1d.ermite.runner.exec.v1.CancelH\x00R\x06cancel\x12G\n" +
1092+ "\fstep_started\x18\x06 \x01(\v2\".ermite.runner.exec.v1.StepStartedH\x00R\vstepStarted\x12M\n" +
1093+ "\x0estep_completed\x18\a \x01(\v2$.ermite.runner.exec.v1.StepCompletedH\x00R\rstepCompleted\x12.\n" +
1094+ "\x03log\x18\b \x01(\v2\x1a.ermite.runner.exec.v1.LogH\x00R\x03log\x12A\n" +
1095+ "\n" +
1096+ "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResultB\x06\n" +
5001097 "\x04body\"W\n" +
5011098 "\x05Hello\x12\x18\n" +
5021099 "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" +
5031100 "\asandbox\x18\x02 \x01(\tR\asandbox\x12\x1a\n" +
504- "\bhostname\x18\x03 \x01(\tR\bhostname\"\x8c\x01\n" +
1101+ "\bhostname\x18\x03 \x01(\tR\bhostname\"\xbe\x01\n" +
5051102 "\x03Run\x12\x16\n" +
5061103 "\x06script\x18\x01 \x01(\tR\x06script\x125\n" +
507- "\x03env\x18\x02 \x03(\v2#.ermite.runner.exec.v1.Run.EnvEntryR\x03env\x1a6\n" +
1104+ "\x03env\x18\x02 \x03(\v2#.ermite.runner.exec.v1.Run.EnvEntryR\x03env\x120\n" +
1105+ "\x03job\x18\x03 \x01(\v2\x1e.ermite.runner.exec.v1.JobSpecR\x03job\x1a6\n" +
5081106 "\bEnvEntry\x12\x10\n" +
5091107 "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
510- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"t\n" +
1108+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xea\x02\n" +
1109+ "\aJobSpec\x12#\n" +
1110+ "\rworkflow_path\x18\x01 \x01(\tR\fworkflowPath\x12#\n" +
1111+ "\rworkflow_yaml\x18\x02 \x01(\tR\fworkflowYaml\x12\x17\n" +
1112+ "\ajob_key\x18\x03 \x01(\tR\x06jobKey\x12\x16\n" +
1113+ "\x06matrix\x18\x04 \x01(\tR\x06matrix\x12\x1a\n" +
1114+ "\bcontexts\x18\x05 \x01(\tR\bcontexts\x12\x12\n" +
1115+ "\x04repo\x18\x06 \x01(\tR\x04repo\x12'\n" +
1116+ "\x0ftimeout_minutes\x18\a \x01(\x05R\x0etimeoutMinutes\x12\x1f\n" +
1117+ "\vrunner_name\x18\b \x01(\tR\n" +
1118+ "runnerName\x12%\n" +
1119+ "\x0erunner_version\x18\t \x01(\tR\rrunnerVersion\x12-\n" +
1120+ "\x12runner_environment\x18\n" +
1121+ " \x01(\tR\x11runnerEnvironment\x12\x14\n" +
1122+ "\x05debug\x18\v \x01(\bR\x05debug\"t\n" +
5111123 "\x06Output\x129\n" +
5121124 "\x06stream\x18\x01 \x01(\x0e2!.ermite.runner.exec.v1.StreamKindR\x06stream\x12\x12\n" +
5131125 "\x04data\x18\x02 \x01(\fR\x04data\x12\x1b\n" +
5141126 "\tunix_nano\x18\x03 \x01(\x03R\bunixNano\"0\n" +
5151127 "\x04Exit\x12\x12\n" +
5161128 "\x04code\x18\x01 \x01(\x05R\x04code\x12\x14\n" +
517- "\x05error\x18\x02 \x01(\tR\x05error\" \n" +
1129+ "\x05error\x18\x02 \x01(\tR\x05error\"W\n" +
5181130 "\x06Cancel\x12\x16\n" +
519- "\x06reason\x18\x01 \x01(\tR\x06reason*Y\n" +
1131+ "\x06reason\x18\x01 \x01(\tR\x06reason\x125\n" +
1132+ "\x04kind\x18\x02 \x01(\x0e2!.ermite.runner.exec.v1.CancelKindR\x04kind\"V\n" +
1133+ "\vStepStarted\x12\x16\n" +
1134+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1135+ "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
1136+ "\tunix_nano\x18\x03 \x01(\x03R\bunixNano\"\x9f\x01\n" +
1137+ "\rStepCompleted\x12\x16\n" +
1138+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1139+ "\x04name\x18\x02 \x01(\tR\x04name\x12E\n" +
1140+ "\n" +
1141+ "conclusion\x18\x03 \x01(\x0e2%.ermite.runner.exec.v1.StepConclusionR\n" +
1142+ "conclusion\x12\x1b\n" +
1143+ "\tunix_nano\x18\x04 \x01(\x03R\bunixNano\"h\n" +
1144+ "\x03Log\x12\x12\n" +
1145+ "\x04step\x18\x01 \x01(\x05R\x04step\x12\x1b\n" +
1146+ "\tunix_nano\x18\x02 \x01(\x03R\bunixNano\x12\x12\n" +
1147+ "\x04text\x18\x03 \x01(\tR\x04text\x12\x1c\n" +
1148+ "\tcontinued\x18\x04 \x01(\bR\tcontinued\"\x96\x02\n" +
1149+ "\tJobResult\x12E\n" +
1150+ "\n" +
1151+ "conclusion\x18\x01 \x01(\x0e2%.ermite.runner.exec.v1.StepConclusionR\n" +
1152+ "conclusion\x12G\n" +
1153+ "\aoutputs\x18\x02 \x03(\v2-.ermite.runner.exec.v1.JobResult.OutputsEntryR\aoutputs\x12\x14\n" +
1154+ "\x05error\x18\x03 \x01(\tR\x05error\x12'\n" +
1155+ "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x1a:\n" +
1156+ "\fOutputsEntry\x12\x10\n" +
1157+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1158+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*Y\n" +
5201159 "\n" +
5211160 "StreamKind\x12\x1b\n" +
5221161 "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" +
5231162 "\x12STREAM_KIND_STDOUT\x10\x01\x12\x16\n" +
524- "\x12STREAM_KIND_STDERR\x10\x02B4Z2bnema.dev/ermite-runner/internal/execstream/execpbb\x06proto3"
1163+ "\x12STREAM_KIND_STDERR\x10\x02*r\n" +
1164+ "\n" +
1165+ "CancelKind\x12\x1b\n" +
1166+ "\x17CANCEL_KIND_UNSPECIFIED\x10\x00\x12\x14\n" +
1167+ "\x10CANCEL_KIND_USER\x10\x01\x12\x17\n" +
1168+ "\x13CANCEL_KIND_TIMEOUT\x10\x02\x12\x18\n" +
1169+ "\x14CANCEL_KIND_SHUTDOWN\x10\x03*\xc6\x01\n" +
1170+ "\x0eStepConclusion\x12\x1f\n" +
1171+ "\x1bSTEP_CONCLUSION_UNSPECIFIED\x10\x00\x12\x1b\n" +
1172+ "\x17STEP_CONCLUSION_SUCCESS\x10\x01\x12\x1b\n" +
1173+ "\x17STEP_CONCLUSION_FAILURE\x10\x02\x12\x1d\n" +
1174+ "\x19STEP_CONCLUSION_CANCELLED\x10\x03\x12\x1b\n" +
1175+ "\x17STEP_CONCLUSION_SKIPPED\x10\x04\x12\x1d\n" +
1176+ "\x19STEP_CONCLUSION_TIMED_OUT\x10\x05B4Z2bnema.dev/ermite-runner/internal/execstream/execpbb\x06proto3"
5251177
5261178 var (
5271179 file_exec_proto_rawDescOnce sync.Once
@@ -535,31 +1187,48 @@ func file_exec_proto_rawDescGZIP() []byte {
5351187 return file_exec_proto_rawDescData
5361188 }
5371189
538-var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
539-var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
1190+var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
1191+var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
5401192 var file_exec_proto_goTypes = []any{
541- (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind
542- (*Frame)(nil), // 1: ermite.runner.exec.v1.Frame
543- (*Hello)(nil), // 2: ermite.runner.exec.v1.Hello
544- (*Run)(nil), // 3: ermite.runner.exec.v1.Run
545- (*Output)(nil), // 4: ermite.runner.exec.v1.Output
546- (*Exit)(nil), // 5: ermite.runner.exec.v1.Exit
547- (*Cancel)(nil), // 6: ermite.runner.exec.v1.Cancel
548- nil, // 7: ermite.runner.exec.v1.Run.EnvEntry
1193+ (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind
1194+ (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind
1195+ (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion
1196+ (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame
1197+ (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello
1198+ (*Run)(nil), // 5: ermite.runner.exec.v1.Run
1199+ (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec
1200+ (*Output)(nil), // 7: ermite.runner.exec.v1.Output
1201+ (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit
1202+ (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel
1203+ (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted
1204+ (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted
1205+ (*Log)(nil), // 12: ermite.runner.exec.v1.Log
1206+ (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult
1207+ nil, // 14: ermite.runner.exec.v1.Run.EnvEntry
1208+ nil, // 15: ermite.runner.exec.v1.JobResult.OutputsEntry
5491209 }
5501210 var file_exec_proto_depIdxs = []int32{
551- 2, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello
552- 3, // 1: ermite.runner.exec.v1.Frame.run:type_name -> ermite.runner.exec.v1.Run
553- 4, // 2: ermite.runner.exec.v1.Frame.output:type_name -> ermite.runner.exec.v1.Output
554- 5, // 3: ermite.runner.exec.v1.Frame.exit:type_name -> ermite.runner.exec.v1.Exit
555- 6, // 4: ermite.runner.exec.v1.Frame.cancel:type_name -> ermite.runner.exec.v1.Cancel
556- 7, // 5: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry
557- 0, // 6: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind
558- 7, // [7:7] is the sub-list for method output_type
559- 7, // [7:7] is the sub-list for method input_type
560- 7, // [7:7] is the sub-list for extension type_name
561- 7, // [7:7] is the sub-list for extension extendee
562- 0, // [0:7] is the sub-list for field type_name
1211+ 4, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello
1212+ 5, // 1: ermite.runner.exec.v1.Frame.run:type_name -> ermite.runner.exec.v1.Run
1213+ 7, // 2: ermite.runner.exec.v1.Frame.output:type_name -> ermite.runner.exec.v1.Output
1214+ 8, // 3: ermite.runner.exec.v1.Frame.exit:type_name -> ermite.runner.exec.v1.Exit
1215+ 9, // 4: ermite.runner.exec.v1.Frame.cancel:type_name -> ermite.runner.exec.v1.Cancel
1216+ 10, // 5: ermite.runner.exec.v1.Frame.step_started:type_name -> ermite.runner.exec.v1.StepStarted
1217+ 11, // 6: ermite.runner.exec.v1.Frame.step_completed:type_name -> ermite.runner.exec.v1.StepCompleted
1218+ 12, // 7: ermite.runner.exec.v1.Frame.log:type_name -> ermite.runner.exec.v1.Log
1219+ 13, // 8: ermite.runner.exec.v1.Frame.job_result:type_name -> ermite.runner.exec.v1.JobResult
1220+ 14, // 9: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry
1221+ 6, // 10: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec
1222+ 0, // 11: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind
1223+ 1, // 12: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind
1224+ 2, // 13: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion
1225+ 2, // 14: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion
1226+ 15, // 15: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry
1227+ 16, // [16:16] is the sub-list for method output_type
1228+ 16, // [16:16] is the sub-list for method input_type
1229+ 16, // [16:16] is the sub-list for extension type_name
1230+ 16, // [16:16] is the sub-list for extension extendee
1231+ 0, // [0:16] is the sub-list for field type_name
5631232 }
5641233
5651234 func init() { file_exec_proto_init() }
@@ -573,14 +1242,18 @@ func file_exec_proto_init() {
5731242 (*Frame_Output)(nil),
5741243 (*Frame_Exit)(nil),
5751244 (*Frame_Cancel)(nil),
1245+ (*Frame_StepStarted)(nil),
1246+ (*Frame_StepCompleted)(nil),
1247+ (*Frame_Log)(nil),
1248+ (*Frame_JobResult)(nil),
5761249 }
5771250 type x struct{}
5781251 out := protoimpl.TypeBuilder{
5791252 File: protoimpl.DescBuilder{
5801253 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
5811254 RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)),
582- NumEnums: 1,
583- NumMessages: 7,
1255+ NumEnums: 3,
1256+ NumMessages: 13,
5841257 NumExtensions: 0,
5851258 NumServices: 0,
5861259 },
@@ -79,6 +79,116 @@ func (StreamKind) EnumDescriptor() ([]byte, []int) {
79 return file_exec_proto_rawDescGZIP(), []int{0}79 return file_exec_proto_rawDescGZIP(), []int{0}
80 }80 }
81 81
82+type CancelKind int32
83+
84+const (
85+ CancelKind_CANCEL_KIND_UNSPECIFIED CancelKind = 0 // same as USER
86+ CancelKind_CANCEL_KIND_USER CancelKind = 1
87+ CancelKind_CANCEL_KIND_TIMEOUT CancelKind = 2
88+ CancelKind_CANCEL_KIND_SHUTDOWN CancelKind = 3
89+)
90+
91+// Enum value maps for CancelKind.
92+var (
93+ CancelKind_name = map[int32]string{
94+ 0: "CANCEL_KIND_UNSPECIFIED",
95+ 1: "CANCEL_KIND_USER",
96+ 2: "CANCEL_KIND_TIMEOUT",
97+ 3: "CANCEL_KIND_SHUTDOWN",
98+ }
99+ CancelKind_value = map[string]int32{
100+ "CANCEL_KIND_UNSPECIFIED": 0,
101+ "CANCEL_KIND_USER": 1,
102+ "CANCEL_KIND_TIMEOUT": 2,
103+ "CANCEL_KIND_SHUTDOWN": 3,
104+ }
105+)
106+
107+func (x CancelKind) Enum() *CancelKind {
108+ p := new(CancelKind)
109+ *p = x
110+ return p
111+}
112+
113+func (x CancelKind) String() string {
114+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
115+}
116+
117+func (CancelKind) Descriptor() protoreflect.EnumDescriptor {
118+ return file_exec_proto_enumTypes[1].Descriptor()
119+}
120+
121+func (CancelKind) Type() protoreflect.EnumType {
122+ return &file_exec_proto_enumTypes[1]
123+}
124+
125+func (x CancelKind) Number() protoreflect.EnumNumber {
126+ return protoreflect.EnumNumber(x)
127+}
128+
129+// Deprecated: Use CancelKind.Descriptor instead.
130+func (CancelKind) EnumDescriptor() ([]byte, []int) {
131+ return file_exec_proto_rawDescGZIP(), []int{1}
132+}
133+
134+type StepConclusion int32
135+
136+const (
137+ StepConclusion_STEP_CONCLUSION_UNSPECIFIED StepConclusion = 0
138+ StepConclusion_STEP_CONCLUSION_SUCCESS StepConclusion = 1
139+ StepConclusion_STEP_CONCLUSION_FAILURE StepConclusion = 2
140+ StepConclusion_STEP_CONCLUSION_CANCELLED StepConclusion = 3
141+ StepConclusion_STEP_CONCLUSION_SKIPPED StepConclusion = 4
142+ StepConclusion_STEP_CONCLUSION_TIMED_OUT StepConclusion = 5 // jobs only
143+)
144+
145+// Enum value maps for StepConclusion.
146+var (
147+ StepConclusion_name = map[int32]string{
148+ 0: "STEP_CONCLUSION_UNSPECIFIED",
149+ 1: "STEP_CONCLUSION_SUCCESS",
150+ 2: "STEP_CONCLUSION_FAILURE",
151+ 3: "STEP_CONCLUSION_CANCELLED",
152+ 4: "STEP_CONCLUSION_SKIPPED",
153+ 5: "STEP_CONCLUSION_TIMED_OUT",
154+ }
155+ StepConclusion_value = map[string]int32{
156+ "STEP_CONCLUSION_UNSPECIFIED": 0,
157+ "STEP_CONCLUSION_SUCCESS": 1,
158+ "STEP_CONCLUSION_FAILURE": 2,
159+ "STEP_CONCLUSION_CANCELLED": 3,
160+ "STEP_CONCLUSION_SKIPPED": 4,
161+ "STEP_CONCLUSION_TIMED_OUT": 5,
162+ }
163+)
164+
165+func (x StepConclusion) Enum() *StepConclusion {
166+ p := new(StepConclusion)
167+ *p = x
168+ return p
169+}
170+
171+func (x StepConclusion) String() string {
172+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
173+}
174+
175+func (StepConclusion) Descriptor() protoreflect.EnumDescriptor {
176+ return file_exec_proto_enumTypes[2].Descriptor()
177+}
178+
179+func (StepConclusion) Type() protoreflect.EnumType {
180+ return &file_exec_proto_enumTypes[2]
181+}
182+
183+func (x StepConclusion) Number() protoreflect.EnumNumber {
184+ return protoreflect.EnumNumber(x)
185+}
186+
187+// Deprecated: Use StepConclusion.Descriptor instead.
188+func (StepConclusion) EnumDescriptor() ([]byte, []int) {
189+ return file_exec_proto_rawDescGZIP(), []int{2}
190+}
191+
82 // Frame is the only message on the stream.192 // Frame is the only message on the stream.
83 type Frame struct {193 type Frame struct {
84 state protoimpl.MessageState `protogen:"open.v1"`194 state protoimpl.MessageState `protogen:"open.v1"`
@@ -89,6 +199,10 @@ type Frame struct {
89 // *Frame_Output199 // *Frame_Output
90 // *Frame_Exit200 // *Frame_Exit
91 // *Frame_Cancel201 // *Frame_Cancel
202+ // *Frame_StepStarted
203+ // *Frame_StepCompleted
204+ // *Frame_Log
205+ // *Frame_JobResult
92 Body isFrame_Body `protobuf_oneof:"body"`206 Body isFrame_Body `protobuf_oneof:"body"`
93 unknownFields protoimpl.UnknownFields207 unknownFields protoimpl.UnknownFields
94 sizeCache protoimpl.SizeCache208 sizeCache protoimpl.SizeCache
@@ -176,6 +290,42 @@ func (x *Frame) GetCancel() *Cancel {
176 return nil290 return nil
177 }291 }
178 292
293+func (x *Frame) GetStepStarted() *StepStarted {
294+ if x != nil {
295+ if x, ok := x.Body.(*Frame_StepStarted); ok {
296+ return x.StepStarted
297+ }
298+ }
299+ return nil
300+}
301+
302+func (x *Frame) GetStepCompleted() *StepCompleted {
303+ if x != nil {
304+ if x, ok := x.Body.(*Frame_StepCompleted); ok {
305+ return x.StepCompleted
306+ }
307+ }
308+ return nil
309+}
310+
311+func (x *Frame) GetLog() *Log {
312+ if x != nil {
313+ if x, ok := x.Body.(*Frame_Log); ok {
314+ return x.Log
315+ }
316+ }
317+ return nil
318+}
319+
320+func (x *Frame) GetJobResult() *JobResult {
321+ if x != nil {
322+ if x, ok := x.Body.(*Frame_JobResult); ok {
323+ return x.JobResult
324+ }
325+ }
326+ return nil
327+}
328+
179 type isFrame_Body interface {329 type isFrame_Body interface {
180 isFrame_Body()330 isFrame_Body()
181 }331 }
@@ -200,6 +350,22 @@ type Frame_Cancel struct {
200 Cancel *Cancel `protobuf:"bytes,5,opt,name=cancel,proto3,oneof"` // host -> executor350 Cancel *Cancel `protobuf:"bytes,5,opt,name=cancel,proto3,oneof"` // host -> executor
201 }351 }
202 352
353+type Frame_StepStarted struct {
354+ StepStarted *StepStarted `protobuf:"bytes,6,opt,name=step_started,json=stepStarted,proto3,oneof"` // executor -> host
355+}
356+
357+type Frame_StepCompleted struct {
358+ StepCompleted *StepCompleted `protobuf:"bytes,7,opt,name=step_completed,json=stepCompleted,proto3,oneof"` // executor -> host
359+}
360+
361+type Frame_Log struct {
362+ Log *Log `protobuf:"bytes,8,opt,name=log,proto3,oneof"` // executor -> host
363+}
364+
365+type Frame_JobResult struct {
366+ JobResult *JobResult `protobuf:"bytes,9,opt,name=job_result,json=jobResult,proto3,oneof"` // executor -> host, before Exit
367+}
368+
203 func (*Frame_Hello) isFrame_Body() {}369 func (*Frame_Hello) isFrame_Body() {}
204 370
205 func (*Frame_Run) isFrame_Body() {}371 func (*Frame_Run) isFrame_Body() {}
@@ -210,6 +376,14 @@ func (*Frame_Exit) isFrame_Body() {}
210 376
211 func (*Frame_Cancel) isFrame_Body() {}377 func (*Frame_Cancel) isFrame_Body() {}
212 378
379+func (*Frame_StepStarted) isFrame_Body() {}
380+
381+func (*Frame_StepCompleted) isFrame_Body() {}
382+
383+func (*Frame_Log) isFrame_Body() {}
384+
385+func (*Frame_JobResult) isFrame_Body() {}
386+
213 type Hello struct {387 type Hello struct {
214 state protoimpl.MessageState `protogen:"open.v1"`388 state protoimpl.MessageState `protogen:"open.v1"`
215 Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor389 Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor
@@ -270,12 +444,16 @@ func (x *Hello) GetHostname() string {
270 return ""444 return ""
271 }445 }
272 446
273-// Run asks the executor to run a script. CI-1 stub executor: the script is run447+// Run asks the executor to run a script or a job. Without job it is the stub
274-// with `sh -c`; an empty script means the built-in diagnostics script.448+// executor of sandbox-check: the script is run with `sh -c`; an empty script
449+// means the built-in diagnostics script. With job the executor runs the job
450+// like a GitHub Actions runner and reports it with StepStarted, Log,
451+// StepCompleted and JobResult frames, then Exit.
275 type Run struct {452 type Run struct {
276 state protoimpl.MessageState `protogen:"open.v1"`453 state protoimpl.MessageState `protogen:"open.v1"`
277 Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`454 Script string `protobuf:"bytes,1,opt,name=script,proto3" json:"script,omitempty"`
278 Env map[string]string `protobuf:"bytes,2,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`455 Env map[string]string `protobuf:"bytes,2,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
456+ Job *JobSpec `protobuf:"bytes,3,opt,name=job,proto3" json:"job,omitempty"`
279 unknownFields protoimpl.UnknownFields457 unknownFields protoimpl.UnknownFields
280 sizeCache protoimpl.SizeCache458 sizeCache protoimpl.SizeCache
281 }459 }
@@ -324,6 +502,140 @@ func (x *Run) GetEnv() map[string]string {
324 return nil502 return nil
325 }503 }
326 504
505+func (x *Run) GetJob() *JobSpec {
506+ if x != nil {
507+ return x.Job
508+ }
509+ return nil
510+}
511+
512+// JobSpec is the job to run: the fields of the job that ermite handed to the
513+// host process, minus the tokens (the sandbox never sees them).
514+type JobSpec struct {
515+ state protoimpl.MessageState `protogen:"open.v1"`
516+ WorkflowPath string `protobuf:"bytes,1,opt,name=workflow_path,json=workflowPath,proto3" json:"workflow_path,omitempty"`
517+ WorkflowYaml string `protobuf:"bytes,2,opt,name=workflow_yaml,json=workflowYaml,proto3" json:"workflow_yaml,omitempty"`
518+ JobKey string `protobuf:"bytes,3,opt,name=job_key,json=jobKey,proto3" json:"job_key,omitempty"`
519+ Matrix string `protobuf:"bytes,4,opt,name=matrix,proto3" json:"matrix,omitempty"` // JSON object
520+ Contexts string `protobuf:"bytes,5,opt,name=contexts,proto3" json:"contexts,omitempty"` // JSON object
521+ Repo string `protobuf:"bytes,6,opt,name=repo,proto3" json:"repo,omitempty"` // repository name
522+ TimeoutMinutes int32 `protobuf:"varint,7,opt,name=timeout_minutes,json=timeoutMinutes,proto3" json:"timeout_minutes,omitempty"`
523+ RunnerName string `protobuf:"bytes,8,opt,name=runner_name,json=runnerName,proto3" json:"runner_name,omitempty"`
524+ RunnerVersion string `protobuf:"bytes,9,opt,name=runner_version,json=runnerVersion,proto3" json:"runner_version,omitempty"`
525+ // runner_environment is the runner.environment value ("self-hosted").
526+ RunnerEnvironment string `protobuf:"bytes,10,opt,name=runner_environment,json=runnerEnvironment,proto3" json:"runner_environment,omitempty"`
527+ Debug bool `protobuf:"varint,11,opt,name=debug,proto3" json:"debug,omitempty"` // ACTIONS_STEP_DEBUG
528+ unknownFields protoimpl.UnknownFields
529+ sizeCache protoimpl.SizeCache
530+}
531+
532+func (x *JobSpec) Reset() {
533+ *x = JobSpec{}
534+ mi := &file_exec_proto_msgTypes[3]
535+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
536+ ms.StoreMessageInfo(mi)
537+}
538+
539+func (x *JobSpec) String() string {
540+ return protoimpl.X.MessageStringOf(x)
541+}
542+
543+func (*JobSpec) ProtoMessage() {}
544+
545+func (x *JobSpec) ProtoReflect() protoreflect.Message {
546+ mi := &file_exec_proto_msgTypes[3]
547+ if x != nil {
548+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
549+ if ms.LoadMessageInfo() == nil {
550+ ms.StoreMessageInfo(mi)
551+ }
552+ return ms
553+ }
554+ return mi.MessageOf(x)
555+}
556+
557+// Deprecated: Use JobSpec.ProtoReflect.Descriptor instead.
558+func (*JobSpec) Descriptor() ([]byte, []int) {
559+ return file_exec_proto_rawDescGZIP(), []int{3}
560+}
561+
562+func (x *JobSpec) GetWorkflowPath() string {
563+ if x != nil {
564+ return x.WorkflowPath
565+ }
566+ return ""
567+}
568+
569+func (x *JobSpec) GetWorkflowYaml() string {
570+ if x != nil {
571+ return x.WorkflowYaml
572+ }
573+ return ""
574+}
575+
576+func (x *JobSpec) GetJobKey() string {
577+ if x != nil {
578+ return x.JobKey
579+ }
580+ return ""
581+}
582+
583+func (x *JobSpec) GetMatrix() string {
584+ if x != nil {
585+ return x.Matrix
586+ }
587+ return ""
588+}
589+
590+func (x *JobSpec) GetContexts() string {
591+ if x != nil {
592+ return x.Contexts
593+ }
594+ return ""
595+}
596+
597+func (x *JobSpec) GetRepo() string {
598+ if x != nil {
599+ return x.Repo
600+ }
601+ return ""
602+}
603+
604+func (x *JobSpec) GetTimeoutMinutes() int32 {
605+ if x != nil {
606+ return x.TimeoutMinutes
607+ }
608+ return 0
609+}
610+
611+func (x *JobSpec) GetRunnerName() string {
612+ if x != nil {
613+ return x.RunnerName
614+ }
615+ return ""
616+}
617+
618+func (x *JobSpec) GetRunnerVersion() string {
619+ if x != nil {
620+ return x.RunnerVersion
621+ }
622+ return ""
623+}
624+
625+func (x *JobSpec) GetRunnerEnvironment() string {
626+ if x != nil {
627+ return x.RunnerEnvironment
628+ }
629+ return ""
630+}
631+
632+func (x *JobSpec) GetDebug() bool {
633+ if x != nil {
634+ return x.Debug
635+ }
636+ return false
637+}
638+
327 type Output struct {639 type Output struct {
328 state protoimpl.MessageState `protogen:"open.v1"`640 state protoimpl.MessageState `protogen:"open.v1"`
329 Stream StreamKind `protobuf:"varint,1,opt,name=stream,proto3,enum=ermite.runner.exec.v1.StreamKind" json:"stream,omitempty"`641 Stream StreamKind `protobuf:"varint,1,opt,name=stream,proto3,enum=ermite.runner.exec.v1.StreamKind" json:"stream,omitempty"`
@@ -335,7 +647,7 @@ type Output struct {
335 647
336 func (x *Output) Reset() {648 func (x *Output) Reset() {
337 *x = Output{}649 *x = Output{}
338- mi := &file_exec_proto_msgTypes[3]650+ mi := &file_exec_proto_msgTypes[4]
339 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))651 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
340 ms.StoreMessageInfo(mi)652 ms.StoreMessageInfo(mi)
341 }653 }
@@ -347,7 +659,7 @@ func (x *Output) String() string {
347 func (*Output) ProtoMessage() {}659 func (*Output) ProtoMessage() {}
348 660
349 func (x *Output) ProtoReflect() protoreflect.Message {661 func (x *Output) ProtoReflect() protoreflect.Message {
350- mi := &file_exec_proto_msgTypes[3]662+ mi := &file_exec_proto_msgTypes[4]
351 if x != nil {663 if x != nil {
352 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))664 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
353 if ms.LoadMessageInfo() == nil {665 if ms.LoadMessageInfo() == nil {
@@ -360,7 +672,7 @@ func (x *Output) ProtoReflect() protoreflect.Message {
360 672
361 // Deprecated: Use Output.ProtoReflect.Descriptor instead.673 // Deprecated: Use Output.ProtoReflect.Descriptor instead.
362 func (*Output) Descriptor() ([]byte, []int) {674 func (*Output) Descriptor() ([]byte, []int) {
363- return file_exec_proto_rawDescGZIP(), []int{3}675+ return file_exec_proto_rawDescGZIP(), []int{4}
364 }676 }
365 677
366 func (x *Output) GetStream() StreamKind {678 func (x *Output) GetStream() StreamKind {
@@ -394,7 +706,7 @@ type Exit struct {
394 706
395 func (x *Exit) Reset() {707 func (x *Exit) Reset() {
396 *x = Exit{}708 *x = Exit{}
397- mi := &file_exec_proto_msgTypes[4]709+ mi := &file_exec_proto_msgTypes[5]
398 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))710 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
399 ms.StoreMessageInfo(mi)711 ms.StoreMessageInfo(mi)
400 }712 }
@@ -406,7 +718,7 @@ func (x *Exit) String() string {
406 func (*Exit) ProtoMessage() {}718 func (*Exit) ProtoMessage() {}
407 719
408 func (x *Exit) ProtoReflect() protoreflect.Message {720 func (x *Exit) ProtoReflect() protoreflect.Message {
409- mi := &file_exec_proto_msgTypes[4]721+ mi := &file_exec_proto_msgTypes[5]
410 if x != nil {722 if x != nil {
411 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))723 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
412 if ms.LoadMessageInfo() == nil {724 if ms.LoadMessageInfo() == nil {
@@ -419,7 +731,7 @@ func (x *Exit) ProtoReflect() protoreflect.Message {
419 731
420 // Deprecated: Use Exit.ProtoReflect.Descriptor instead.732 // Deprecated: Use Exit.ProtoReflect.Descriptor instead.
421 func (*Exit) Descriptor() ([]byte, []int) {733 func (*Exit) Descriptor() ([]byte, []int) {
422- return file_exec_proto_rawDescGZIP(), []int{4}734+ return file_exec_proto_rawDescGZIP(), []int{5}
423 }735 }
424 736
425 func (x *Exit) GetCode() int32 {737 func (x *Exit) GetCode() int32 {
@@ -443,14 +755,17 @@ type Cancel struct {
443 state protoimpl.MessageState `protogen:"open.v1"`755 state protoimpl.MessageState `protogen:"open.v1"`
444 // reason is for the job log and diagnostics ("cancelled by the user",756 // reason is for the job log and diagnostics ("cancelled by the user",
445 // "job timeout", ...); it never changes the behaviour.757 // "job timeout", ...); it never changes the behaviour.
446- Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"`758+ Reason string `protobuf:"bytes,1,opt,name=reason,proto3" json:"reason,omitempty"`
759+ // kind tells the job executor how the job ends: cancelled, timed out, or
760+ // cancelled because the runner shuts down. The stub executor ignores it.
761+ Kind CancelKind `protobuf:"varint,2,opt,name=kind,proto3,enum=ermite.runner.exec.v1.CancelKind" json:"kind,omitempty"`
447 unknownFields protoimpl.UnknownFields762 unknownFields protoimpl.UnknownFields
448 sizeCache protoimpl.SizeCache763 sizeCache protoimpl.SizeCache
449 }764 }
450 765
451 func (x *Cancel) Reset() {766 func (x *Cancel) Reset() {
452 *x = Cancel{}767 *x = Cancel{}
453- mi := &file_exec_proto_msgTypes[5]768+ mi := &file_exec_proto_msgTypes[6]
454 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))769 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
455 ms.StoreMessageInfo(mi)770 ms.StoreMessageInfo(mi)
456 }771 }
@@ -462,7 +777,7 @@ func (x *Cancel) String() string {
462 func (*Cancel) ProtoMessage() {}777 func (*Cancel) ProtoMessage() {}
463 778
464 func (x *Cancel) ProtoReflect() protoreflect.Message {779 func (x *Cancel) ProtoReflect() protoreflect.Message {
465- mi := &file_exec_proto_msgTypes[5]780+ mi := &file_exec_proto_msgTypes[6]
466 if x != nil {781 if x != nil {
467 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))782 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
468 if ms.LoadMessageInfo() == nil {783 if ms.LoadMessageInfo() == nil {
@@ -475,7 +790,7 @@ func (x *Cancel) ProtoReflect() protoreflect.Message {
475 790
476 // Deprecated: Use Cancel.ProtoReflect.Descriptor instead.791 // Deprecated: Use Cancel.ProtoReflect.Descriptor instead.
477 func (*Cancel) Descriptor() ([]byte, []int) {792 func (*Cancel) Descriptor() ([]byte, []int) {
478- return file_exec_proto_rawDescGZIP(), []int{5}793+ return file_exec_proto_rawDescGZIP(), []int{6}
479 }794 }
480 795
481 func (x *Cancel) GetReason() string {796 func (x *Cancel) GetReason() string {
@@ -485,43 +800,380 @@ func (x *Cancel) GetReason() string {
485 return ""800 return ""
486 }801 }
487 802
803+func (x *Cancel) GetKind() CancelKind {
804+ if x != nil {
805+ return x.Kind
806+ }
807+ return CancelKind_CANCEL_KIND_UNSPECIFIED
808+}
809+
810+// Steps are numbered like GitHub's log: "Set up job" is 1, the steps of the
811+// workflow follow, "Complete job" is last.
812+type StepStarted struct {
813+ state protoimpl.MessageState `protogen:"open.v1"`
814+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
815+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
816+ UnixNano int64 `protobuf:"varint,3,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
817+ unknownFields protoimpl.UnknownFields
818+ sizeCache protoimpl.SizeCache
819+}
820+
821+func (x *StepStarted) Reset() {
822+ *x = StepStarted{}
823+ mi := &file_exec_proto_msgTypes[7]
824+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
825+ ms.StoreMessageInfo(mi)
826+}
827+
828+func (x *StepStarted) String() string {
829+ return protoimpl.X.MessageStringOf(x)
830+}
831+
832+func (*StepStarted) ProtoMessage() {}
833+
834+func (x *StepStarted) ProtoReflect() protoreflect.Message {
835+ mi := &file_exec_proto_msgTypes[7]
836+ if x != nil {
837+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
838+ if ms.LoadMessageInfo() == nil {
839+ ms.StoreMessageInfo(mi)
840+ }
841+ return ms
842+ }
843+ return mi.MessageOf(x)
844+}
845+
846+// Deprecated: Use StepStarted.ProtoReflect.Descriptor instead.
847+func (*StepStarted) Descriptor() ([]byte, []int) {
848+ return file_exec_proto_rawDescGZIP(), []int{7}
849+}
850+
851+func (x *StepStarted) GetNumber() int32 {
852+ if x != nil {
853+ return x.Number
854+ }
855+ return 0
856+}
857+
858+func (x *StepStarted) GetName() string {
859+ if x != nil {
860+ return x.Name
861+ }
862+ return ""
863+}
864+
865+func (x *StepStarted) GetUnixNano() int64 {
866+ if x != nil {
867+ return x.UnixNano
868+ }
869+ return 0
870+}
871+
872+type StepCompleted struct {
873+ state protoimpl.MessageState `protogen:"open.v1"`
874+ Number int32 `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
875+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
876+ Conclusion StepConclusion `protobuf:"varint,3,opt,name=conclusion,proto3,enum=ermite.runner.exec.v1.StepConclusion" json:"conclusion,omitempty"`
877+ UnixNano int64 `protobuf:"varint,4,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
878+ unknownFields protoimpl.UnknownFields
879+ sizeCache protoimpl.SizeCache
880+}
881+
882+func (x *StepCompleted) Reset() {
883+ *x = StepCompleted{}
884+ mi := &file_exec_proto_msgTypes[8]
885+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
886+ ms.StoreMessageInfo(mi)
887+}
888+
889+func (x *StepCompleted) String() string {
890+ return protoimpl.X.MessageStringOf(x)
891+}
892+
893+func (*StepCompleted) ProtoMessage() {}
894+
895+func (x *StepCompleted) ProtoReflect() protoreflect.Message {
896+ mi := &file_exec_proto_msgTypes[8]
897+ if x != nil {
898+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
899+ if ms.LoadMessageInfo() == nil {
900+ ms.StoreMessageInfo(mi)
901+ }
902+ return ms
903+ }
904+ return mi.MessageOf(x)
905+}
906+
907+// Deprecated: Use StepCompleted.ProtoReflect.Descriptor instead.
908+func (*StepCompleted) Descriptor() ([]byte, []int) {
909+ return file_exec_proto_rawDescGZIP(), []int{8}
910+}
911+
912+func (x *StepCompleted) GetNumber() int32 {
913+ if x != nil {
914+ return x.Number
915+ }
916+ return 0
917+}
918+
919+func (x *StepCompleted) GetName() string {
920+ if x != nil {
921+ return x.Name
922+ }
923+ return ""
924+}
925+
926+func (x *StepCompleted) GetConclusion() StepConclusion {
927+ if x != nil {
928+ return x.Conclusion
929+ }
930+ return StepConclusion_STEP_CONCLUSION_UNSPECIFIED
931+}
932+
933+func (x *StepCompleted) GetUnixNano() int64 {
934+ if x != nil {
935+ return x.UnixNano
936+ }
937+ return 0
938+}
939+
940+// Log is one line of the job log, without terminator, valid UTF-8. A line of
941+// more than 64 KiB is cut by the executor into pieces at character boundaries;
942+// every piece but the first has continued set (the AppendLog rule).
943+type Log struct {
944+ state protoimpl.MessageState `protogen:"open.v1"`
945+ Step int32 `protobuf:"varint,1,opt,name=step,proto3" json:"step,omitempty"`
946+ UnixNano int64 `protobuf:"varint,2,opt,name=unix_nano,json=unixNano,proto3" json:"unix_nano,omitempty"`
947+ Text string `protobuf:"bytes,3,opt,name=text,proto3" json:"text,omitempty"`
948+ Continued bool `protobuf:"varint,4,opt,name=continued,proto3" json:"continued,omitempty"`
949+ unknownFields protoimpl.UnknownFields
950+ sizeCache protoimpl.SizeCache
951+}
952+
953+func (x *Log) Reset() {
954+ *x = Log{}
955+ mi := &file_exec_proto_msgTypes[9]
956+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
957+ ms.StoreMessageInfo(mi)
958+}
959+
960+func (x *Log) String() string {
961+ return protoimpl.X.MessageStringOf(x)
962+}
963+
964+func (*Log) ProtoMessage() {}
965+
966+func (x *Log) ProtoReflect() protoreflect.Message {
967+ mi := &file_exec_proto_msgTypes[9]
968+ if x != nil {
969+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
970+ if ms.LoadMessageInfo() == nil {
971+ ms.StoreMessageInfo(mi)
972+ }
973+ return ms
974+ }
975+ return mi.MessageOf(x)
976+}
977+
978+// Deprecated: Use Log.ProtoReflect.Descriptor instead.
979+func (*Log) Descriptor() ([]byte, []int) {
980+ return file_exec_proto_rawDescGZIP(), []int{9}
981+}
982+
983+func (x *Log) GetStep() int32 {
984+ if x != nil {
985+ return x.Step
986+ }
987+ return 0
988+}
989+
990+func (x *Log) GetUnixNano() int64 {
991+ if x != nil {
992+ return x.UnixNano
993+ }
994+ return 0
995+}
996+
997+func (x *Log) GetText() string {
998+ if x != nil {
999+ return x.Text
1000+ }
1001+ return ""
1002+}
1003+
1004+func (x *Log) GetContinued() bool {
1005+ if x != nil {
1006+ return x.Continued
1007+ }
1008+ return false
1009+}
1010+
1011+// JobResult is the outcome of the job; Exit follows.
1012+type JobResult struct {
1013+ state protoimpl.MessageState `protogen:"open.v1"`
1014+ Conclusion StepConclusion `protobuf:"varint,1,opt,name=conclusion,proto3,enum=ermite.runner.exec.v1.StepConclusion" json:"conclusion,omitempty"`
1015+ Outputs map[string]string `protobuf:"bytes,2,rep,name=outputs,proto3" json:"outputs,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
1016+ Error string `protobuf:"bytes,3,opt,name=error,proto3" json:"error,omitempty"`
1017+ RunnerShutdown bool `protobuf:"varint,4,opt,name=runner_shutdown,json=runnerShutdown,proto3" json:"runner_shutdown,omitempty"`
1018+ unknownFields protoimpl.UnknownFields
1019+ sizeCache protoimpl.SizeCache
1020+}
1021+
1022+func (x *JobResult) Reset() {
1023+ *x = JobResult{}
1024+ mi := &file_exec_proto_msgTypes[10]
1025+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1026+ ms.StoreMessageInfo(mi)
1027+}
1028+
1029+func (x *JobResult) String() string {
1030+ return protoimpl.X.MessageStringOf(x)
1031+}
1032+
1033+func (*JobResult) ProtoMessage() {}
1034+
1035+func (x *JobResult) ProtoReflect() protoreflect.Message {
1036+ mi := &file_exec_proto_msgTypes[10]
1037+ if x != nil {
1038+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
1039+ if ms.LoadMessageInfo() == nil {
1040+ ms.StoreMessageInfo(mi)
1041+ }
1042+ return ms
1043+ }
1044+ return mi.MessageOf(x)
1045+}
1046+
1047+// Deprecated: Use JobResult.ProtoReflect.Descriptor instead.
1048+func (*JobResult) Descriptor() ([]byte, []int) {
1049+ return file_exec_proto_rawDescGZIP(), []int{10}
1050+}
1051+
1052+func (x *JobResult) GetConclusion() StepConclusion {
1053+ if x != nil {
1054+ return x.Conclusion
1055+ }
1056+ return StepConclusion_STEP_CONCLUSION_UNSPECIFIED
1057+}
1058+
1059+func (x *JobResult) GetOutputs() map[string]string {
1060+ if x != nil {
1061+ return x.Outputs
1062+ }
1063+ return nil
1064+}
1065+
1066+func (x *JobResult) GetError() string {
1067+ if x != nil {
1068+ return x.Error
1069+ }
1070+ return ""
1071+}
1072+
1073+func (x *JobResult) GetRunnerShutdown() bool {
1074+ if x != nil {
1075+ return x.RunnerShutdown
1076+ }
1077+ return false
1078+}
1079+
488 var File_exec_proto protoreflect.FileDescriptor1080 var File_exec_proto protoreflect.FileDescriptor
489 1081
490 const file_exec_proto_rawDesc = "" +1082 const file_exec_proto_rawDesc = "" +
491 "\n" +1083 "\n" +
492 "\n" +1084 "\n" +
493- "exec.proto\x12\x15ermite.runner.exec.v1\"\x9a\x02\n" +1085+ "exec.proto\x12\x15ermite.runner.exec.v1\"\xa5\x04\n" +
494 "\x05Frame\x124\n" +1086 "\x05Frame\x124\n" +
495 "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" +1087 "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" +
496 "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" +1088 "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" +
497 "\x06output\x18\x03 \x01(\v2\x1d.ermite.runner.exec.v1.OutputH\x00R\x06output\x121\n" +1089 "\x06output\x18\x03 \x01(\v2\x1d.ermite.runner.exec.v1.OutputH\x00R\x06output\x121\n" +
498 "\x04exit\x18\x04 \x01(\v2\x1b.ermite.runner.exec.v1.ExitH\x00R\x04exit\x127\n" +1090 "\x04exit\x18\x04 \x01(\v2\x1b.ermite.runner.exec.v1.ExitH\x00R\x04exit\x127\n" +
499- "\x06cancel\x18\x05 \x01(\v2\x1d.ermite.runner.exec.v1.CancelH\x00R\x06cancelB\x06\n" +1091+ "\x06cancel\x18\x05 \x01(\v2\x1d.ermite.runner.exec.v1.CancelH\x00R\x06cancel\x12G\n" +
1092+ "\fstep_started\x18\x06 \x01(\v2\".ermite.runner.exec.v1.StepStartedH\x00R\vstepStarted\x12M\n" +
1093+ "\x0estep_completed\x18\a \x01(\v2$.ermite.runner.exec.v1.StepCompletedH\x00R\rstepCompleted\x12.\n" +
1094+ "\x03log\x18\b \x01(\v2\x1a.ermite.runner.exec.v1.LogH\x00R\x03log\x12A\n" +
1095+ "\n" +
1096+ "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResultB\x06\n" +
500 "\x04body\"W\n" +1097 "\x04body\"W\n" +
501 "\x05Hello\x12\x18\n" +1098 "\x05Hello\x12\x18\n" +
502 "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" +1099 "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" +
503 "\asandbox\x18\x02 \x01(\tR\asandbox\x12\x1a\n" +1100 "\asandbox\x18\x02 \x01(\tR\asandbox\x12\x1a\n" +
504- "\bhostname\x18\x03 \x01(\tR\bhostname\"\x8c\x01\n" +1101+ "\bhostname\x18\x03 \x01(\tR\bhostname\"\xbe\x01\n" +
505 "\x03Run\x12\x16\n" +1102 "\x03Run\x12\x16\n" +
506 "\x06script\x18\x01 \x01(\tR\x06script\x125\n" +1103 "\x06script\x18\x01 \x01(\tR\x06script\x125\n" +
507- "\x03env\x18\x02 \x03(\v2#.ermite.runner.exec.v1.Run.EnvEntryR\x03env\x1a6\n" +1104+ "\x03env\x18\x02 \x03(\v2#.ermite.runner.exec.v1.Run.EnvEntryR\x03env\x120\n" +
1105+ "\x03job\x18\x03 \x01(\v2\x1e.ermite.runner.exec.v1.JobSpecR\x03job\x1a6\n" +
508 "\bEnvEntry\x12\x10\n" +1106 "\bEnvEntry\x12\x10\n" +
509 "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +1107 "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
510- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"t\n" +1108+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xea\x02\n" +
1109+ "\aJobSpec\x12#\n" +
1110+ "\rworkflow_path\x18\x01 \x01(\tR\fworkflowPath\x12#\n" +
1111+ "\rworkflow_yaml\x18\x02 \x01(\tR\fworkflowYaml\x12\x17\n" +
1112+ "\ajob_key\x18\x03 \x01(\tR\x06jobKey\x12\x16\n" +
1113+ "\x06matrix\x18\x04 \x01(\tR\x06matrix\x12\x1a\n" +
1114+ "\bcontexts\x18\x05 \x01(\tR\bcontexts\x12\x12\n" +
1115+ "\x04repo\x18\x06 \x01(\tR\x04repo\x12'\n" +
1116+ "\x0ftimeout_minutes\x18\a \x01(\x05R\x0etimeoutMinutes\x12\x1f\n" +
1117+ "\vrunner_name\x18\b \x01(\tR\n" +
1118+ "runnerName\x12%\n" +
1119+ "\x0erunner_version\x18\t \x01(\tR\rrunnerVersion\x12-\n" +
1120+ "\x12runner_environment\x18\n" +
1121+ " \x01(\tR\x11runnerEnvironment\x12\x14\n" +
1122+ "\x05debug\x18\v \x01(\bR\x05debug\"t\n" +
511 "\x06Output\x129\n" +1123 "\x06Output\x129\n" +
512 "\x06stream\x18\x01 \x01(\x0e2!.ermite.runner.exec.v1.StreamKindR\x06stream\x12\x12\n" +1124 "\x06stream\x18\x01 \x01(\x0e2!.ermite.runner.exec.v1.StreamKindR\x06stream\x12\x12\n" +
513 "\x04data\x18\x02 \x01(\fR\x04data\x12\x1b\n" +1125 "\x04data\x18\x02 \x01(\fR\x04data\x12\x1b\n" +
514 "\tunix_nano\x18\x03 \x01(\x03R\bunixNano\"0\n" +1126 "\tunix_nano\x18\x03 \x01(\x03R\bunixNano\"0\n" +
515 "\x04Exit\x12\x12\n" +1127 "\x04Exit\x12\x12\n" +
516 "\x04code\x18\x01 \x01(\x05R\x04code\x12\x14\n" +1128 "\x04code\x18\x01 \x01(\x05R\x04code\x12\x14\n" +
517- "\x05error\x18\x02 \x01(\tR\x05error\" \n" +1129+ "\x05error\x18\x02 \x01(\tR\x05error\"W\n" +
518 "\x06Cancel\x12\x16\n" +1130 "\x06Cancel\x12\x16\n" +
519- "\x06reason\x18\x01 \x01(\tR\x06reason*Y\n" +1131+ "\x06reason\x18\x01 \x01(\tR\x06reason\x125\n" +
1132+ "\x04kind\x18\x02 \x01(\x0e2!.ermite.runner.exec.v1.CancelKindR\x04kind\"V\n" +
1133+ "\vStepStarted\x12\x16\n" +
1134+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1135+ "\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
1136+ "\tunix_nano\x18\x03 \x01(\x03R\bunixNano\"\x9f\x01\n" +
1137+ "\rStepCompleted\x12\x16\n" +
1138+ "\x06number\x18\x01 \x01(\x05R\x06number\x12\x12\n" +
1139+ "\x04name\x18\x02 \x01(\tR\x04name\x12E\n" +
1140+ "\n" +
1141+ "conclusion\x18\x03 \x01(\x0e2%.ermite.runner.exec.v1.StepConclusionR\n" +
1142+ "conclusion\x12\x1b\n" +
1143+ "\tunix_nano\x18\x04 \x01(\x03R\bunixNano\"h\n" +
1144+ "\x03Log\x12\x12\n" +
1145+ "\x04step\x18\x01 \x01(\x05R\x04step\x12\x1b\n" +
1146+ "\tunix_nano\x18\x02 \x01(\x03R\bunixNano\x12\x12\n" +
1147+ "\x04text\x18\x03 \x01(\tR\x04text\x12\x1c\n" +
1148+ "\tcontinued\x18\x04 \x01(\bR\tcontinued\"\x96\x02\n" +
1149+ "\tJobResult\x12E\n" +
1150+ "\n" +
1151+ "conclusion\x18\x01 \x01(\x0e2%.ermite.runner.exec.v1.StepConclusionR\n" +
1152+ "conclusion\x12G\n" +
1153+ "\aoutputs\x18\x02 \x03(\v2-.ermite.runner.exec.v1.JobResult.OutputsEntryR\aoutputs\x12\x14\n" +
1154+ "\x05error\x18\x03 \x01(\tR\x05error\x12'\n" +
1155+ "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x1a:\n" +
1156+ "\fOutputsEntry\x12\x10\n" +
1157+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
1158+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*Y\n" +
520 "\n" +1159 "\n" +
521 "StreamKind\x12\x1b\n" +1160 "StreamKind\x12\x1b\n" +
522 "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" +1161 "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" +
523 "\x12STREAM_KIND_STDOUT\x10\x01\x12\x16\n" +1162 "\x12STREAM_KIND_STDOUT\x10\x01\x12\x16\n" +
524- "\x12STREAM_KIND_STDERR\x10\x02B4Z2bnema.dev/ermite-runner/internal/execstream/execpbb\x06proto3"1163+ "\x12STREAM_KIND_STDERR\x10\x02*r\n" +
1164+ "\n" +
1165+ "CancelKind\x12\x1b\n" +
1166+ "\x17CANCEL_KIND_UNSPECIFIED\x10\x00\x12\x14\n" +
1167+ "\x10CANCEL_KIND_USER\x10\x01\x12\x17\n" +
1168+ "\x13CANCEL_KIND_TIMEOUT\x10\x02\x12\x18\n" +
1169+ "\x14CANCEL_KIND_SHUTDOWN\x10\x03*\xc6\x01\n" +
1170+ "\x0eStepConclusion\x12\x1f\n" +
1171+ "\x1bSTEP_CONCLUSION_UNSPECIFIED\x10\x00\x12\x1b\n" +
1172+ "\x17STEP_CONCLUSION_SUCCESS\x10\x01\x12\x1b\n" +
1173+ "\x17STEP_CONCLUSION_FAILURE\x10\x02\x12\x1d\n" +
1174+ "\x19STEP_CONCLUSION_CANCELLED\x10\x03\x12\x1b\n" +
1175+ "\x17STEP_CONCLUSION_SKIPPED\x10\x04\x12\x1d\n" +
1176+ "\x19STEP_CONCLUSION_TIMED_OUT\x10\x05B4Z2bnema.dev/ermite-runner/internal/execstream/execpbb\x06proto3"
525 1177
526 var (1178 var (
527 file_exec_proto_rawDescOnce sync.Once1179 file_exec_proto_rawDescOnce sync.Once
@@ -535,31 +1187,48 @@ func file_exec_proto_rawDescGZIP() []byte {
535 return file_exec_proto_rawDescData1187 return file_exec_proto_rawDescData
536 }1188 }
537 1189
538-var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 1)1190+var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
539-var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 7)1191+var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
540 var file_exec_proto_goTypes = []any{1192 var file_exec_proto_goTypes = []any{
541- (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind1193+ (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind
542- (*Frame)(nil), // 1: ermite.runner.exec.v1.Frame1194+ (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind
543- (*Hello)(nil), // 2: ermite.runner.exec.v1.Hello1195+ (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion
544- (*Run)(nil), // 3: ermite.runner.exec.v1.Run1196+ (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame
545- (*Output)(nil), // 4: ermite.runner.exec.v1.Output1197+ (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello
546- (*Exit)(nil), // 5: ermite.runner.exec.v1.Exit1198+ (*Run)(nil), // 5: ermite.runner.exec.v1.Run
547- (*Cancel)(nil), // 6: ermite.runner.exec.v1.Cancel1199+ (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec
548- nil, // 7: ermite.runner.exec.v1.Run.EnvEntry1200+ (*Output)(nil), // 7: ermite.runner.exec.v1.Output
1201+ (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit
1202+ (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel
1203+ (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted
1204+ (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted
1205+ (*Log)(nil), // 12: ermite.runner.exec.v1.Log
1206+ (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult
1207+ nil, // 14: ermite.runner.exec.v1.Run.EnvEntry
1208+ nil, // 15: ermite.runner.exec.v1.JobResult.OutputsEntry
549 }1209 }
550 var file_exec_proto_depIdxs = []int32{1210 var file_exec_proto_depIdxs = []int32{
551- 2, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello1211+ 4, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello
552- 3, // 1: ermite.runner.exec.v1.Frame.run:type_name -> ermite.runner.exec.v1.Run1212+ 5, // 1: ermite.runner.exec.v1.Frame.run:type_name -> ermite.runner.exec.v1.Run
553- 4, // 2: ermite.runner.exec.v1.Frame.output:type_name -> ermite.runner.exec.v1.Output1213+ 7, // 2: ermite.runner.exec.v1.Frame.output:type_name -> ermite.runner.exec.v1.Output
554- 5, // 3: ermite.runner.exec.v1.Frame.exit:type_name -> ermite.runner.exec.v1.Exit1214+ 8, // 3: ermite.runner.exec.v1.Frame.exit:type_name -> ermite.runner.exec.v1.Exit
555- 6, // 4: ermite.runner.exec.v1.Frame.cancel:type_name -> ermite.runner.exec.v1.Cancel1215+ 9, // 4: ermite.runner.exec.v1.Frame.cancel:type_name -> ermite.runner.exec.v1.Cancel
556- 7, // 5: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry1216+ 10, // 5: ermite.runner.exec.v1.Frame.step_started:type_name -> ermite.runner.exec.v1.StepStarted
557- 0, // 6: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind1217+ 11, // 6: ermite.runner.exec.v1.Frame.step_completed:type_name -> ermite.runner.exec.v1.StepCompleted
558- 7, // [7:7] is the sub-list for method output_type1218+ 12, // 7: ermite.runner.exec.v1.Frame.log:type_name -> ermite.runner.exec.v1.Log
559- 7, // [7:7] is the sub-list for method input_type1219+ 13, // 8: ermite.runner.exec.v1.Frame.job_result:type_name -> ermite.runner.exec.v1.JobResult
560- 7, // [7:7] is the sub-list for extension type_name1220+ 14, // 9: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry
561- 7, // [7:7] is the sub-list for extension extendee1221+ 6, // 10: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec
562- 0, // [0:7] is the sub-list for field type_name1222+ 0, // 11: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind
1223+ 1, // 12: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind
1224+ 2, // 13: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion
1225+ 2, // 14: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion
1226+ 15, // 15: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry
1227+ 16, // [16:16] is the sub-list for method output_type
1228+ 16, // [16:16] is the sub-list for method input_type
1229+ 16, // [16:16] is the sub-list for extension type_name
1230+ 16, // [16:16] is the sub-list for extension extendee
1231+ 0, // [0:16] is the sub-list for field type_name
563 }1232 }
564 1233
565 func init() { file_exec_proto_init() }1234 func init() { file_exec_proto_init() }
@@ -573,14 +1242,18 @@ func file_exec_proto_init() {
573 (*Frame_Output)(nil),1242 (*Frame_Output)(nil),
574 (*Frame_Exit)(nil),1243 (*Frame_Exit)(nil),
575 (*Frame_Cancel)(nil),1244 (*Frame_Cancel)(nil),
1245+ (*Frame_StepStarted)(nil),
1246+ (*Frame_StepCompleted)(nil),
1247+ (*Frame_Log)(nil),
1248+ (*Frame_JobResult)(nil),
576 }1249 }
577 type x struct{}1250 type x struct{}
578 out := protoimpl.TypeBuilder{1251 out := protoimpl.TypeBuilder{
579 File: protoimpl.DescBuilder{1252 File: protoimpl.DescBuilder{
580 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),1253 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
581 RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)),1254 RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)),
582- NumEnums: 1,1255+ NumEnums: 3,
583- NumMessages: 7,1256+ NumMessages: 13,
584 NumExtensions: 0,1257 NumExtensions: 0,
585 NumServices: 0,1258 NumServices: 0,
586 },1259 },
M internal/execstream/frame.go +39 -2
@@ -100,6 +100,24 @@ func (c *Conn) Recv() (*execpb.Frame, error) {
100100 // a Cancel; the caller bounds that wait by stopping the sandbox, which closes
101101 // the stream and makes Drive return an error.
102102 func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOutput func(*execpb.Output)) (*execpb.Hello, *execpb.Exit, error) {
103+ return DriveJob(rw, run, cancel, JobHandler{Output: onOutput})
104+}
105+
106+// JobHandler receives the frames of a session. Nil functions ignore their
107+// frames. The functions are called from the goroutine of DriveJob, in the
108+// order of the stream.
109+type JobHandler struct {
110+ Hello func(*execpb.Hello)
111+ Output func(*execpb.Output)
112+ StepStarted func(*execpb.StepStarted)
113+ StepCompleted func(*execpb.StepCompleted)
114+ Log func(*execpb.Log)
115+ JobResult func(*execpb.JobResult)
116+}
117+
118+// DriveJob is Drive for a session that carries a job: besides Output frames it
119+// delivers the step, log and result frames to h.
120+func DriveJob(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, h JobHandler) (*execpb.Hello, *execpb.Exit, error) {
103121 c := NewConn(rw)
104122 first, err := c.Recv()
105123 if err != nil {
@@ -109,6 +127,9 @@ func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOu
109127 if hello == nil {
110128 return nil, nil, fmt.Errorf("execstream: first frame is not hello")
111129 }
130+ if h.Hello != nil {
131+ h.Hello(hello)
132+ }
112133 if err := c.Send(&execpb.Frame{Body: &execpb.Frame_Run{Run: run}}); err != nil {
113134 return hello, nil, fmt.Errorf("execstream: sending run: %w", err)
114135 }
@@ -137,8 +158,24 @@ func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOu
137158 }
138159 switch b := f.Body.(type) {
139160 case *execpb.Frame_Output:
140- if onOutput != nil {
141- onOutput(b.Output)
161+ if h.Output != nil {
162+ h.Output(b.Output)
163+ }
164+ case *execpb.Frame_StepStarted:
165+ if h.StepStarted != nil {
166+ h.StepStarted(b.StepStarted)
167+ }
168+ case *execpb.Frame_StepCompleted:
169+ if h.StepCompleted != nil {
170+ h.StepCompleted(b.StepCompleted)
171+ }
172+ case *execpb.Frame_Log:
173+ if h.Log != nil {
174+ h.Log(b.Log)
175+ }
176+ case *execpb.Frame_JobResult:
177+ if h.JobResult != nil {
178+ h.JobResult(b.JobResult)
142179 }
143180 case *execpb.Frame_Exit:
144181 return hello, b.Exit, nil
@@ -100,6 +100,24 @@ func (c *Conn) Recv() (*execpb.Frame, error) {
100 // a Cancel; the caller bounds that wait by stopping the sandbox, which closes100 // a Cancel; the caller bounds that wait by stopping the sandbox, which closes
101 // the stream and makes Drive return an error.101 // the stream and makes Drive return an error.
102 func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOutput func(*execpb.Output)) (*execpb.Hello, *execpb.Exit, error) {102 func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOutput func(*execpb.Output)) (*execpb.Hello, *execpb.Exit, error) {
103+ return DriveJob(rw, run, cancel, JobHandler{Output: onOutput})
104+}
105+
106+// JobHandler receives the frames of a session. Nil functions ignore their
107+// frames. The functions are called from the goroutine of DriveJob, in the
108+// order of the stream.
109+type JobHandler struct {
110+ Hello func(*execpb.Hello)
111+ Output func(*execpb.Output)
112+ StepStarted func(*execpb.StepStarted)
113+ StepCompleted func(*execpb.StepCompleted)
114+ Log func(*execpb.Log)
115+ JobResult func(*execpb.JobResult)
116+}
117+
118+// DriveJob is Drive for a session that carries a job: besides Output frames it
119+// delivers the step, log and result frames to h.
120+func DriveJob(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, h JobHandler) (*execpb.Hello, *execpb.Exit, error) {
103 c := NewConn(rw)121 c := NewConn(rw)
104 first, err := c.Recv()122 first, err := c.Recv()
105 if err != nil {123 if err != nil {
@@ -109,6 +127,9 @@ func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOu
109 if hello == nil {127 if hello == nil {
110 return nil, nil, fmt.Errorf("execstream: first frame is not hello")128 return nil, nil, fmt.Errorf("execstream: first frame is not hello")
111 }129 }
130+ if h.Hello != nil {
131+ h.Hello(hello)
132+ }
112 if err := c.Send(&execpb.Frame{Body: &execpb.Frame_Run{Run: run}}); err != nil {133 if err := c.Send(&execpb.Frame{Body: &execpb.Frame_Run{Run: run}}); err != nil {
113 return hello, nil, fmt.Errorf("execstream: sending run: %w", err)134 return hello, nil, fmt.Errorf("execstream: sending run: %w", err)
114 }135 }
@@ -137,8 +158,24 @@ func Drive(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, onOu
137 }158 }
138 switch b := f.Body.(type) {159 switch b := f.Body.(type) {
139 case *execpb.Frame_Output:160 case *execpb.Frame_Output:
140- if onOutput != nil {161+ if h.Output != nil {
141- onOutput(b.Output)162+ h.Output(b.Output)
163+ }
164+ case *execpb.Frame_StepStarted:
165+ if h.StepStarted != nil {
166+ h.StepStarted(b.StepStarted)
167+ }
168+ case *execpb.Frame_StepCompleted:
169+ if h.StepCompleted != nil {
170+ h.StepCompleted(b.StepCompleted)
171+ }
172+ case *execpb.Frame_Log:
173+ if h.Log != nil {
174+ h.Log(b.Log)
175+ }
176+ case *execpb.Frame_JobResult:
177+ if h.JobResult != nil {
178+ h.JobResult(b.JobResult)
142 }179 }
143 case *execpb.Frame_Exit:180 case *execpb.Frame_Exit:
144 return hello, b.Exit, nil181 return hello, b.Exit, nil
M internal/execstream/frame_test.go +36 -0
@@ -8,6 +8,7 @@ import (
88 "errors"
99 "io"
1010 "net"
11+ "strings"
1112 "testing"
1213 "testing/iotest"
1314 "time"
@@ -136,3 +137,38 @@ func TestDriveHelloMissing(t *testing.T) {
136137 t.Fatal("want error when the first frame is not hello")
137138 }
138139 }
140+
141+func TestDriveJobDeliversJobFrames(t *testing.T) {
142+ host, guest := net.Pipe()
143+ go func() {
144+ g := NewConn(guest)
145+ _ = g.Send(&execpb.Frame{Body: &execpb.Frame_Hello{Hello: &execpb.Hello{Version: "v"}}})
146+ r, _ := g.Recv()
147+ if r.GetRun().GetJob().GetJobKey() != "build" {
148+ t.Errorf("run: %v", r)
149+ }
150+ for _, b := range []*execpb.Frame{
151+ {Body: &execpb.Frame_StepStarted{StepStarted: &execpb.StepStarted{Number: 1, Name: "Set up job"}}},
152+ {Body: &execpb.Frame_Log{Log: &execpb.Log{Step: 1, Text: "hello", UnixNano: 5}}},
153+ {Body: &execpb.Frame_StepCompleted{StepCompleted: &execpb.StepCompleted{Number: 1, Conclusion: execpb.StepConclusion_STEP_CONCLUSION_SUCCESS}}},
154+ {Body: &execpb.Frame_JobResult{JobResult: &execpb.JobResult{Conclusion: execpb.StepConclusion_STEP_CONCLUSION_SUCCESS, Outputs: map[string]string{"a": "b"}}}},
155+ {Body: &execpb.Frame_Exit{Exit: &execpb.Exit{}}},
156+ } {
157+ _ = g.Send(b)
158+ }
159+ }()
160+ var got []string
161+ _, exit, err := DriveJob(host, &execpb.Run{Job: &execpb.JobSpec{JobKey: "build"}}, nil, JobHandler{
162+ StepStarted: func(s *execpb.StepStarted) { got = append(got, "start:"+s.Name) },
163+ Log: func(l *execpb.Log) { got = append(got, "log:"+l.Text) },
164+ StepCompleted: func(s *execpb.StepCompleted) { got = append(got, "done:"+s.Conclusion.String()) },
165+ JobResult: func(r *execpb.JobResult) { got = append(got, "result:"+r.Outputs["a"]) },
166+ })
167+ if err != nil || exit == nil {
168+ t.Fatal(err)
169+ }
170+ want := "start:Set up job log:hello done:STEP_CONCLUSION_SUCCESS result:b"
171+ if strings.Join(got, " ") != want {
172+ t.Fatalf("%v", got)
173+ }
174+}
@@ -8,6 +8,7 @@ import (
8 "errors"8 "errors"
9 "io"9 "io"
10 "net"10 "net"
11+ "strings"
11 "testing"12 "testing"
12 "testing/iotest"13 "testing/iotest"
13 "time"14 "time"
@@ -136,3 +137,38 @@ func TestDriveHelloMissing(t *testing.T) {
136 t.Fatal("want error when the first frame is not hello")137 t.Fatal("want error when the first frame is not hello")
137 }138 }
138 }139 }
140+
141+func TestDriveJobDeliversJobFrames(t *testing.T) {
142+ host, guest := net.Pipe()
143+ go func() {
144+ g := NewConn(guest)
145+ _ = g.Send(&execpb.Frame{Body: &execpb.Frame_Hello{Hello: &execpb.Hello{Version: "v"}}})
146+ r, _ := g.Recv()
147+ if r.GetRun().GetJob().GetJobKey() != "build" {
148+ t.Errorf("run: %v", r)
149+ }
150+ for _, b := range []*execpb.Frame{
151+ {Body: &execpb.Frame_StepStarted{StepStarted: &execpb.StepStarted{Number: 1, Name: "Set up job"}}},
152+ {Body: &execpb.Frame_Log{Log: &execpb.Log{Step: 1, Text: "hello", UnixNano: 5}}},
153+ {Body: &execpb.Frame_StepCompleted{StepCompleted: &execpb.StepCompleted{Number: 1, Conclusion: execpb.StepConclusion_STEP_CONCLUSION_SUCCESS}}},
154+ {Body: &execpb.Frame_JobResult{JobResult: &execpb.JobResult{Conclusion: execpb.StepConclusion_STEP_CONCLUSION_SUCCESS, Outputs: map[string]string{"a": "b"}}}},
155+ {Body: &execpb.Frame_Exit{Exit: &execpb.Exit{}}},
156+ } {
157+ _ = g.Send(b)
158+ }
159+ }()
160+ var got []string
161+ _, exit, err := DriveJob(host, &execpb.Run{Job: &execpb.JobSpec{JobKey: "build"}}, nil, JobHandler{
162+ StepStarted: func(s *execpb.StepStarted) { got = append(got, "start:"+s.Name) },
163+ Log: func(l *execpb.Log) { got = append(got, "log:"+l.Text) },
164+ StepCompleted: func(s *execpb.StepCompleted) { got = append(got, "done:"+s.Conclusion.String()) },
165+ JobResult: func(r *execpb.JobResult) { got = append(got, "result:"+r.Outputs["a"]) },
166+ })
167+ if err != nil || exit == nil {
168+ t.Fatal(err)
169+ }
170+ want := "start:Set up job log:hello done:STEP_CONCLUSION_SUCCESS result:b"
171+ if strings.Join(got, " ") != want {
172+ t.Fatalf("%v", got)
173+ }
174+}

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 4

Tags 11

↑↓ moveenter openesc close