feat(host): resolve actions for the executor and give the job token to github.token
- commit
- a6671fa0b094e8c5e44fd3f4f42897f03900c42c
- author
- brice · · 2026-10-13 15:16 UTC
- parent
- 1396410
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
12 files · +803 -36
M internal/execstream/exec.proto +32 -0
| @@ -25,6 +25,8 @@ message Frame { | |||
| 25 | 25 | StepCompleted step_completed = 7; // executor -> host | |
| 26 | 26 | Log log = 8; // executor -> host | |
| 27 | 27 | JobResult job_result = 9; // executor -> host, before Exit | |
| 28 | + | ResolveRequest resolve_request = 10; // executor -> host | |
| 29 | + | ResolveResponse resolve_response = 11; // host -> executor, answers a ResolveRequest | |
| 28 | 30 | } | |
| 29 | 31 | } | |
| 30 | 32 | ||
| @@ -145,3 +147,33 @@ message JobResult { | |||
| 145 | 147 | string error = 3; | |
| 146 | 148 | bool runner_shutdown = 4; | |
| 147 | 149 | } | |
| 150 | + | ||
| 151 | + | // ResolveRequest asks the host to resolve actions with RunnerService. | |
| 152 | + | // ResolveActions: the job token stays in the host, so the executor cannot call | |
| 153 | + | // it. The executor sends one per level of composite actions (the runner's own | |
| 154 | + | // "one more round per level") and waits for the ResolveResponse with the same id. | |
| 155 | + | message ResolveRequest { | |
| 156 | + | int64 id = 1; | |
| 157 | + | repeated string uses = 2; // "owner/repo[/path]@ref", at most 100 | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | // ResolveResponse answers a ResolveRequest. error is set when the call itself | |
| 161 | + | // failed (the executor fails the job); actions has one entry per uses otherwise. | |
| 162 | + | message ResolveResponse { | |
| 163 | + | int64 id = 1; | |
| 164 | + | string error = 2; | |
| 165 | + | repeated ResolvedAction actions = 3; | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | // ResolvedAction mirrors ermite.ResolvedAction (the token is a secret: the | |
| 169 | + | // executor masks it). | |
| 170 | + | message ResolvedAction { | |
| 171 | + | string ref = 1; | |
| 172 | + | string name_with_owner = 2; | |
| 173 | + | string path = 3; | |
| 174 | + | string resolved_sha = 4; | |
| 175 | + | string tarball_url = 5; | |
| 176 | + | string token = 6; | |
| 177 | + | string source = 7; | |
| 178 | + | string error = 8; | |
| 179 | + | } | |
| @@ -25,6 +25,8 @@ message Frame { | |||||
| 25 | StepCompleted step_completed = 7; // executor -> host | 25 | StepCompleted step_completed = 7; // executor -> host | ||
| 26 | Log log = 8; // executor -> host | 26 | Log log = 8; // executor -> host | ||
| 27 | JobResult job_result = 9; // executor -> host, before Exit | 27 | JobResult job_result = 9; // executor -> host, before Exit | ||
| 28 | + | ResolveRequest resolve_request = 10; // executor -> host | |||
| 29 | + | ResolveResponse resolve_response = 11; // host -> executor, answers a ResolveRequest | |||
| 28 | } | 30 | } | ||
| 29 | } | 31 | } | ||
| 30 | 32 | ||||
| @@ -145,3 +147,33 @@ message JobResult { | |||||
| 145 | string error = 3; | 147 | string error = 3; | ||
| 146 | bool runner_shutdown = 4; | 148 | bool runner_shutdown = 4; | ||
| 147 | } | 149 | } | ||
| 150 | + | ||||
| 151 | + | // ResolveRequest asks the host to resolve actions with RunnerService. | |||
| 152 | + | // ResolveActions: the job token stays in the host, so the executor cannot call | |||
| 153 | + | // it. The executor sends one per level of composite actions (the runner's own | |||
| 154 | + | // "one more round per level") and waits for the ResolveResponse with the same id. | |||
| 155 | + | message ResolveRequest { | |||
| 156 | + | int64 id = 1; | |||
| 157 | + | repeated string uses = 2; // "owner/repo[/path]@ref", at most 100 | |||
| 158 | + | } | |||
| 159 | + | ||||
| 160 | + | // ResolveResponse answers a ResolveRequest. error is set when the call itself | |||
| 161 | + | // failed (the executor fails the job); actions has one entry per uses otherwise. | |||
| 162 | + | message ResolveResponse { | |||
| 163 | + | int64 id = 1; | |||
| 164 | + | string error = 2; | |||
| 165 | + | repeated ResolvedAction actions = 3; | |||
| 166 | + | } | |||
| 167 | + | ||||
| 168 | + | // ResolvedAction mirrors ermite.ResolvedAction (the token is a secret: the | |||
| 169 | + | // executor masks it). | |||
| 170 | + | message ResolvedAction { | |||
| 171 | + | string ref = 1; | |||
| 172 | + | string name_with_owner = 2; | |||
| 173 | + | string path = 3; | |||
| 174 | + | string resolved_sha = 4; | |||
| 175 | + | string tarball_url = 5; | |||
| 176 | + | string token = 6; | |||
| 177 | + | string source = 7; | |||
| 178 | + | string error = 8; | |||
| 179 | + | } | |||
M internal/execstream/execpb/exec.pb.go +313 -33
| @@ -203,6 +203,8 @@ type Frame struct { | |||
| 203 | 203 | // *Frame_StepCompleted | |
| 204 | 204 | // *Frame_Log | |
| 205 | 205 | // *Frame_JobResult | |
| 206 | + | // *Frame_ResolveRequest | |
| 207 | + | // *Frame_ResolveResponse | |
| 206 | 208 | Body isFrame_Body `protobuf_oneof:"body"` | |
| 207 | 209 | unknownFields protoimpl.UnknownFields | |
| 208 | 210 | sizeCache protoimpl.SizeCache | |
| @@ -326,6 +328,24 @@ func (x *Frame) GetJobResult() *JobResult { | |||
| 326 | 328 | return nil | |
| 327 | 329 | } | |
| 328 | 330 | ||
| 331 | + | func (x *Frame) GetResolveRequest() *ResolveRequest { | |
| 332 | + | if x != nil { | |
| 333 | + | if x, ok := x.Body.(*Frame_ResolveRequest); ok { | |
| 334 | + | return x.ResolveRequest | |
| 335 | + | } | |
| 336 | + | } | |
| 337 | + | return nil | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | func (x *Frame) GetResolveResponse() *ResolveResponse { | |
| 341 | + | if x != nil { | |
| 342 | + | if x, ok := x.Body.(*Frame_ResolveResponse); ok { | |
| 343 | + | return x.ResolveResponse | |
| 344 | + | } | |
| 345 | + | } | |
| 346 | + | return nil | |
| 347 | + | } | |
| 348 | + | ||
| 329 | 349 | type isFrame_Body interface { | |
| 330 | 350 | isFrame_Body() | |
| 331 | 351 | } | |
| @@ -366,6 +386,14 @@ type Frame_JobResult struct { | |||
| 366 | 386 | JobResult *JobResult `protobuf:"bytes,9,opt,name=job_result,json=jobResult,proto3,oneof"` // executor -> host, before Exit | |
| 367 | 387 | } | |
| 368 | 388 | ||
| 389 | + | type Frame_ResolveRequest struct { | |
| 390 | + | ResolveRequest *ResolveRequest `protobuf:"bytes,10,opt,name=resolve_request,json=resolveRequest,proto3,oneof"` // executor -> host | |
| 391 | + | } | |
| 392 | + | ||
| 393 | + | type Frame_ResolveResponse struct { | |
| 394 | + | ResolveResponse *ResolveResponse `protobuf:"bytes,11,opt,name=resolve_response,json=resolveResponse,proto3,oneof"` // host -> executor, answers a ResolveRequest | |
| 395 | + | } | |
| 396 | + | ||
| 369 | 397 | func (*Frame_Hello) isFrame_Body() {} | |
| 370 | 398 | ||
| 371 | 399 | func (*Frame_Run) isFrame_Body() {} | |
| @@ -384,6 +412,10 @@ func (*Frame_Log) isFrame_Body() {} | |||
| 384 | 412 | ||
| 385 | 413 | func (*Frame_JobResult) isFrame_Body() {} | |
| 386 | 414 | ||
| 415 | + | func (*Frame_ResolveRequest) isFrame_Body() {} | |
| 416 | + | ||
| 417 | + | func (*Frame_ResolveResponse) isFrame_Body() {} | |
| 418 | + | ||
| 387 | 419 | type Hello struct { | |
| 388 | 420 | state protoimpl.MessageState `protogen:"open.v1"` | |
| 389 | 421 | Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor | |
| @@ -1090,12 +1122,232 @@ func (x *JobResult) GetRunnerShutdown() bool { | |||
| 1090 | 1122 | return false | |
| 1091 | 1123 | } | |
| 1092 | 1124 | ||
| 1125 | + | // ResolveRequest asks the host to resolve actions with RunnerService. | |
| 1126 | + | // ResolveActions: the job token stays in the host, so the executor cannot call | |
| 1127 | + | // it. The executor sends one per level of composite actions (the runner's own | |
| 1128 | + | // "one more round per level") and waits for the ResolveResponse with the same id. | |
| 1129 | + | type ResolveRequest struct { | |
| 1130 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1131 | + | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` | |
| 1132 | + | Uses []string `protobuf:"bytes,2,rep,name=uses,proto3" json:"uses,omitempty"` // "owner/repo[/path]@ref", at most 100 | |
| 1133 | + | unknownFields protoimpl.UnknownFields | |
| 1134 | + | sizeCache protoimpl.SizeCache | |
| 1135 | + | } | |
| 1136 | + | ||
| 1137 | + | func (x *ResolveRequest) Reset() { | |
| 1138 | + | *x = ResolveRequest{} | |
| 1139 | + | mi := &file_exec_proto_msgTypes[11] | |
| 1140 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1141 | + | ms.StoreMessageInfo(mi) | |
| 1142 | + | } | |
| 1143 | + | ||
| 1144 | + | func (x *ResolveRequest) String() string { | |
| 1145 | + | return protoimpl.X.MessageStringOf(x) | |
| 1146 | + | } | |
| 1147 | + | ||
| 1148 | + | func (*ResolveRequest) ProtoMessage() {} | |
| 1149 | + | ||
| 1150 | + | func (x *ResolveRequest) ProtoReflect() protoreflect.Message { | |
| 1151 | + | mi := &file_exec_proto_msgTypes[11] | |
| 1152 | + | if x != nil { | |
| 1153 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1154 | + | if ms.LoadMessageInfo() == nil { | |
| 1155 | + | ms.StoreMessageInfo(mi) | |
| 1156 | + | } | |
| 1157 | + | return ms | |
| 1158 | + | } | |
| 1159 | + | return mi.MessageOf(x) | |
| 1160 | + | } | |
| 1161 | + | ||
| 1162 | + | // Deprecated: Use ResolveRequest.ProtoReflect.Descriptor instead. | |
| 1163 | + | func (*ResolveRequest) Descriptor() ([]byte, []int) { | |
| 1164 | + | return file_exec_proto_rawDescGZIP(), []int{11} | |
| 1165 | + | } | |
| 1166 | + | ||
| 1167 | + | func (x *ResolveRequest) GetId() int64 { | |
| 1168 | + | if x != nil { | |
| 1169 | + | return x.Id | |
| 1170 | + | } | |
| 1171 | + | return 0 | |
| 1172 | + | } | |
| 1173 | + | ||
| 1174 | + | func (x *ResolveRequest) GetUses() []string { | |
| 1175 | + | if x != nil { | |
| 1176 | + | return x.Uses | |
| 1177 | + | } | |
| 1178 | + | return nil | |
| 1179 | + | } | |
| 1180 | + | ||
| 1181 | + | // ResolveResponse answers a ResolveRequest. error is set when the call itself | |
| 1182 | + | // failed (the executor fails the job); actions has one entry per uses otherwise. | |
| 1183 | + | type ResolveResponse struct { | |
| 1184 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1185 | + | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` | |
| 1186 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |
| 1187 | + | Actions []*ResolvedAction `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"` | |
| 1188 | + | unknownFields protoimpl.UnknownFields | |
| 1189 | + | sizeCache protoimpl.SizeCache | |
| 1190 | + | } | |
| 1191 | + | ||
| 1192 | + | func (x *ResolveResponse) Reset() { | |
| 1193 | + | *x = ResolveResponse{} | |
| 1194 | + | mi := &file_exec_proto_msgTypes[12] | |
| 1195 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1196 | + | ms.StoreMessageInfo(mi) | |
| 1197 | + | } | |
| 1198 | + | ||
| 1199 | + | func (x *ResolveResponse) String() string { | |
| 1200 | + | return protoimpl.X.MessageStringOf(x) | |
| 1201 | + | } | |
| 1202 | + | ||
| 1203 | + | func (*ResolveResponse) ProtoMessage() {} | |
| 1204 | + | ||
| 1205 | + | func (x *ResolveResponse) ProtoReflect() protoreflect.Message { | |
| 1206 | + | mi := &file_exec_proto_msgTypes[12] | |
| 1207 | + | if x != nil { | |
| 1208 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1209 | + | if ms.LoadMessageInfo() == nil { | |
| 1210 | + | ms.StoreMessageInfo(mi) | |
| 1211 | + | } | |
| 1212 | + | return ms | |
| 1213 | + | } | |
| 1214 | + | return mi.MessageOf(x) | |
| 1215 | + | } | |
| 1216 | + | ||
| 1217 | + | // Deprecated: Use ResolveResponse.ProtoReflect.Descriptor instead. | |
| 1218 | + | func (*ResolveResponse) Descriptor() ([]byte, []int) { | |
| 1219 | + | return file_exec_proto_rawDescGZIP(), []int{12} | |
| 1220 | + | } | |
| 1221 | + | ||
| 1222 | + | func (x *ResolveResponse) GetId() int64 { | |
| 1223 | + | if x != nil { | |
| 1224 | + | return x.Id | |
| 1225 | + | } | |
| 1226 | + | return 0 | |
| 1227 | + | } | |
| 1228 | + | ||
| 1229 | + | func (x *ResolveResponse) GetError() string { | |
| 1230 | + | if x != nil { | |
| 1231 | + | return x.Error | |
| 1232 | + | } | |
| 1233 | + | return "" | |
| 1234 | + | } | |
| 1235 | + | ||
| 1236 | + | func (x *ResolveResponse) GetActions() []*ResolvedAction { | |
| 1237 | + | if x != nil { | |
| 1238 | + | return x.Actions | |
| 1239 | + | } | |
| 1240 | + | return nil | |
| 1241 | + | } | |
| 1242 | + | ||
| 1243 | + | // ResolvedAction mirrors ermite.ResolvedAction (the token is a secret: the | |
| 1244 | + | // executor masks it). | |
| 1245 | + | type ResolvedAction struct { | |
| 1246 | + | state protoimpl.MessageState `protogen:"open.v1"` | |
| 1247 | + | Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` | |
| 1248 | + | NameWithOwner string `protobuf:"bytes,2,opt,name=name_with_owner,json=nameWithOwner,proto3" json:"name_with_owner,omitempty"` | |
| 1249 | + | Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` | |
| 1250 | + | ResolvedSha string `protobuf:"bytes,4,opt,name=resolved_sha,json=resolvedSha,proto3" json:"resolved_sha,omitempty"` | |
| 1251 | + | TarballUrl string `protobuf:"bytes,5,opt,name=tarball_url,json=tarballUrl,proto3" json:"tarball_url,omitempty"` | |
| 1252 | + | Token string `protobuf:"bytes,6,opt,name=token,proto3" json:"token,omitempty"` | |
| 1253 | + | Source string `protobuf:"bytes,7,opt,name=source,proto3" json:"source,omitempty"` | |
| 1254 | + | Error string `protobuf:"bytes,8,opt,name=error,proto3" json:"error,omitempty"` | |
| 1255 | + | unknownFields protoimpl.UnknownFields | |
| 1256 | + | sizeCache protoimpl.SizeCache | |
| 1257 | + | } | |
| 1258 | + | ||
| 1259 | + | func (x *ResolvedAction) Reset() { | |
| 1260 | + | *x = ResolvedAction{} | |
| 1261 | + | mi := &file_exec_proto_msgTypes[13] | |
| 1262 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1263 | + | ms.StoreMessageInfo(mi) | |
| 1264 | + | } | |
| 1265 | + | ||
| 1266 | + | func (x *ResolvedAction) String() string { | |
| 1267 | + | return protoimpl.X.MessageStringOf(x) | |
| 1268 | + | } | |
| 1269 | + | ||
| 1270 | + | func (*ResolvedAction) ProtoMessage() {} | |
| 1271 | + | ||
| 1272 | + | func (x *ResolvedAction) ProtoReflect() protoreflect.Message { | |
| 1273 | + | mi := &file_exec_proto_msgTypes[13] | |
| 1274 | + | if x != nil { | |
| 1275 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |
| 1276 | + | if ms.LoadMessageInfo() == nil { | |
| 1277 | + | ms.StoreMessageInfo(mi) | |
| 1278 | + | } | |
| 1279 | + | return ms | |
| 1280 | + | } | |
| 1281 | + | return mi.MessageOf(x) | |
| 1282 | + | } | |
| 1283 | + | ||
| 1284 | + | // Deprecated: Use ResolvedAction.ProtoReflect.Descriptor instead. | |
| 1285 | + | func (*ResolvedAction) Descriptor() ([]byte, []int) { | |
| 1286 | + | return file_exec_proto_rawDescGZIP(), []int{13} | |
| 1287 | + | } | |
| 1288 | + | ||
| 1289 | + | func (x *ResolvedAction) GetRef() string { | |
| 1290 | + | if x != nil { | |
| 1291 | + | return x.Ref | |
| 1292 | + | } | |
| 1293 | + | return "" | |
| 1294 | + | } | |
| 1295 | + | ||
| 1296 | + | func (x *ResolvedAction) GetNameWithOwner() string { | |
| 1297 | + | if x != nil { | |
| 1298 | + | return x.NameWithOwner | |
| 1299 | + | } | |
| 1300 | + | return "" | |
| 1301 | + | } | |
| 1302 | + | ||
| 1303 | + | func (x *ResolvedAction) GetPath() string { | |
| 1304 | + | if x != nil { | |
| 1305 | + | return x.Path | |
| 1306 | + | } | |
| 1307 | + | return "" | |
| 1308 | + | } | |
| 1309 | + | ||
| 1310 | + | func (x *ResolvedAction) GetResolvedSha() string { | |
| 1311 | + | if x != nil { | |
| 1312 | + | return x.ResolvedSha | |
| 1313 | + | } | |
| 1314 | + | return "" | |
| 1315 | + | } | |
| 1316 | + | ||
| 1317 | + | func (x *ResolvedAction) GetTarballUrl() string { | |
| 1318 | + | if x != nil { | |
| 1319 | + | return x.TarballUrl | |
| 1320 | + | } | |
| 1321 | + | return "" | |
| 1322 | + | } | |
| 1323 | + | ||
| 1324 | + | func (x *ResolvedAction) GetToken() string { | |
| 1325 | + | if x != nil { | |
| 1326 | + | return x.Token | |
| 1327 | + | } | |
| 1328 | + | return "" | |
| 1329 | + | } | |
| 1330 | + | ||
| 1331 | + | func (x *ResolvedAction) GetSource() string { | |
| 1332 | + | if x != nil { | |
| 1333 | + | return x.Source | |
| 1334 | + | } | |
| 1335 | + | return "" | |
| 1336 | + | } | |
| 1337 | + | ||
| 1338 | + | func (x *ResolvedAction) GetError() string { | |
| 1339 | + | if x != nil { | |
| 1340 | + | return x.Error | |
| 1341 | + | } | |
| 1342 | + | return "" | |
| 1343 | + | } | |
| 1344 | + | ||
| 1093 | 1345 | var File_exec_proto protoreflect.FileDescriptor | |
| 1094 | 1346 | ||
| 1095 | 1347 | const file_exec_proto_rawDesc = "" + | |
| 1096 | 1348 | "\n" + | |
| 1097 | 1349 | "\n" + | |
| 1098 | - | "exec.proto\x12\x15ermite.runner.exec.v1\"\xa5\x04\n" + | |
| 1350 | + | "exec.proto\x12\x15ermite.runner.exec.v1\"\xcc\x05\n" + | |
| 1099 | 1351 | "\x05Frame\x124\n" + | |
| 1100 | 1352 | "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" + | |
| 1101 | 1353 | "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" + | |
| @@ -1106,7 +1358,10 @@ const file_exec_proto_rawDesc = "" + | |||
| 1106 | 1358 | "\x0estep_completed\x18\a \x01(\v2$.ermite.runner.exec.v1.StepCompletedH\x00R\rstepCompleted\x12.\n" + | |
| 1107 | 1359 | "\x03log\x18\b \x01(\v2\x1a.ermite.runner.exec.v1.LogH\x00R\x03log\x12A\n" + | |
| 1108 | 1360 | "\n" + | |
| 1109 | - | "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResultB\x06\n" + | |
| 1361 | + | "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResult\x12P\n" + | |
| 1362 | + | "\x0fresolve_request\x18\n" + | |
| 1363 | + | " \x01(\v2%.ermite.runner.exec.v1.ResolveRequestH\x00R\x0eresolveRequest\x12S\n" + | |
| 1364 | + | "\x10resolve_response\x18\v \x01(\v2&.ermite.runner.exec.v1.ResolveResponseH\x00R\x0fresolveResponseB\x06\n" + | |
| 1110 | 1365 | "\x04body\"W\n" + | |
| 1111 | 1366 | "\x05Hello\x12\x18\n" + | |
| 1112 | 1367 | "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + | |
| @@ -1170,7 +1425,24 @@ const file_exec_proto_rawDesc = "" + | |||
| 1170 | 1425 | "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x1a:\n" + | |
| 1171 | 1426 | "\fOutputsEntry\x12\x10\n" + | |
| 1172 | 1427 | "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + | |
| 1173 | - | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*Y\n" + | |
| 1428 | + | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"4\n" + | |
| 1429 | + | "\x0eResolveRequest\x12\x0e\n" + | |
| 1430 | + | "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + | |
| 1431 | + | "\x04uses\x18\x02 \x03(\tR\x04uses\"x\n" + | |
| 1432 | + | "\x0fResolveResponse\x12\x0e\n" + | |
| 1433 | + | "\x02id\x18\x01 \x01(\x03R\x02id\x12\x14\n" + | |
| 1434 | + | "\x05error\x18\x02 \x01(\tR\x05error\x12?\n" + | |
| 1435 | + | "\aactions\x18\x03 \x03(\v2%.ermite.runner.exec.v1.ResolvedActionR\aactions\"\xe6\x01\n" + | |
| 1436 | + | "\x0eResolvedAction\x12\x10\n" + | |
| 1437 | + | "\x03ref\x18\x01 \x01(\tR\x03ref\x12&\n" + | |
| 1438 | + | "\x0fname_with_owner\x18\x02 \x01(\tR\rnameWithOwner\x12\x12\n" + | |
| 1439 | + | "\x04path\x18\x03 \x01(\tR\x04path\x12!\n" + | |
| 1440 | + | "\fresolved_sha\x18\x04 \x01(\tR\vresolvedSha\x12\x1f\n" + | |
| 1441 | + | "\vtarball_url\x18\x05 \x01(\tR\n" + | |
| 1442 | + | "tarballUrl\x12\x14\n" + | |
| 1443 | + | "\x05token\x18\x06 \x01(\tR\x05token\x12\x16\n" + | |
| 1444 | + | "\x06source\x18\a \x01(\tR\x06source\x12\x14\n" + | |
| 1445 | + | "\x05error\x18\b \x01(\tR\x05error*Y\n" + | |
| 1174 | 1446 | "\n" + | |
| 1175 | 1447 | "StreamKind\x12\x1b\n" + | |
| 1176 | 1448 | "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" + | |
| @@ -1203,24 +1475,27 @@ func file_exec_proto_rawDescGZIP() []byte { | |||
| 1203 | 1475 | } | |
| 1204 | 1476 | ||
| 1205 | 1477 | var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 3) | |
| 1206 | - | var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 13) | |
| 1478 | + | var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 16) | |
| 1207 | 1479 | var file_exec_proto_goTypes = []any{ | |
| 1208 | - | (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind | |
| 1209 | - | (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind | |
| 1210 | - | (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion | |
| 1211 | - | (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame | |
| 1212 | - | (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello | |
| 1213 | - | (*Run)(nil), // 5: ermite.runner.exec.v1.Run | |
| 1214 | - | (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec | |
| 1215 | - | (*Output)(nil), // 7: ermite.runner.exec.v1.Output | |
| 1216 | - | (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit | |
| 1217 | - | (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel | |
| 1218 | - | (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted | |
| 1219 | - | (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted | |
| 1220 | - | (*Log)(nil), // 12: ermite.runner.exec.v1.Log | |
| 1221 | - | (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult | |
| 1222 | - | nil, // 14: ermite.runner.exec.v1.Run.EnvEntry | |
| 1223 | - | nil, // 15: ermite.runner.exec.v1.JobResult.OutputsEntry | |
| 1480 | + | (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind | |
| 1481 | + | (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind | |
| 1482 | + | (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion | |
| 1483 | + | (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame | |
| 1484 | + | (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello | |
| 1485 | + | (*Run)(nil), // 5: ermite.runner.exec.v1.Run | |
| 1486 | + | (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec | |
| 1487 | + | (*Output)(nil), // 7: ermite.runner.exec.v1.Output | |
| 1488 | + | (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit | |
| 1489 | + | (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel | |
| 1490 | + | (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted | |
| 1491 | + | (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted | |
| 1492 | + | (*Log)(nil), // 12: ermite.runner.exec.v1.Log | |
| 1493 | + | (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult | |
| 1494 | + | (*ResolveRequest)(nil), // 14: ermite.runner.exec.v1.ResolveRequest | |
| 1495 | + | (*ResolveResponse)(nil), // 15: ermite.runner.exec.v1.ResolveResponse | |
| 1496 | + | (*ResolvedAction)(nil), // 16: ermite.runner.exec.v1.ResolvedAction | |
| 1497 | + | nil, // 17: ermite.runner.exec.v1.Run.EnvEntry | |
| 1498 | + | nil, // 18: ermite.runner.exec.v1.JobResult.OutputsEntry | |
| 1224 | 1499 | } | |
| 1225 | 1500 | var file_exec_proto_depIdxs = []int32{ | |
| 1226 | 1501 | 4, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello | |
| @@ -1232,18 +1507,21 @@ var file_exec_proto_depIdxs = []int32{ | |||
| 1232 | 1507 | 11, // 6: ermite.runner.exec.v1.Frame.step_completed:type_name -> ermite.runner.exec.v1.StepCompleted | |
| 1233 | 1508 | 12, // 7: ermite.runner.exec.v1.Frame.log:type_name -> ermite.runner.exec.v1.Log | |
| 1234 | 1509 | 13, // 8: ermite.runner.exec.v1.Frame.job_result:type_name -> ermite.runner.exec.v1.JobResult | |
| 1235 | - | 14, // 9: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry | |
| 1236 | - | 6, // 10: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec | |
| 1237 | - | 0, // 11: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind | |
| 1238 | - | 1, // 12: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind | |
| 1239 | - | 2, // 13: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | |
| 1240 | - | 2, // 14: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | |
| 1241 | - | 15, // 15: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry | |
| 1242 | - | 16, // [16:16] is the sub-list for method output_type | |
| 1243 | - | 16, // [16:16] is the sub-list for method input_type | |
| 1244 | - | 16, // [16:16] is the sub-list for extension type_name | |
| 1245 | - | 16, // [16:16] is the sub-list for extension extendee | |
| 1246 | - | 0, // [0:16] is the sub-list for field type_name | |
| 1510 | + | 14, // 9: ermite.runner.exec.v1.Frame.resolve_request:type_name -> ermite.runner.exec.v1.ResolveRequest | |
| 1511 | + | 15, // 10: ermite.runner.exec.v1.Frame.resolve_response:type_name -> ermite.runner.exec.v1.ResolveResponse | |
| 1512 | + | 17, // 11: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry | |
| 1513 | + | 6, // 12: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec | |
| 1514 | + | 0, // 13: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind | |
| 1515 | + | 1, // 14: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind | |
| 1516 | + | 2, // 15: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | |
| 1517 | + | 2, // 16: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | |
| 1518 | + | 18, // 17: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry | |
| 1519 | + | 16, // 18: ermite.runner.exec.v1.ResolveResponse.actions:type_name -> ermite.runner.exec.v1.ResolvedAction | |
| 1520 | + | 19, // [19:19] is the sub-list for method output_type | |
| 1521 | + | 19, // [19:19] is the sub-list for method input_type | |
| 1522 | + | 19, // [19:19] is the sub-list for extension type_name | |
| 1523 | + | 19, // [19:19] is the sub-list for extension extendee | |
| 1524 | + | 0, // [0:19] is the sub-list for field type_name | |
| 1247 | 1525 | } | |
| 1248 | 1526 | ||
| 1249 | 1527 | func init() { file_exec_proto_init() } | |
| @@ -1261,6 +1539,8 @@ func file_exec_proto_init() { | |||
| 1261 | 1539 | (*Frame_StepCompleted)(nil), | |
| 1262 | 1540 | (*Frame_Log)(nil), | |
| 1263 | 1541 | (*Frame_JobResult)(nil), | |
| 1542 | + | (*Frame_ResolveRequest)(nil), | |
| 1543 | + | (*Frame_ResolveResponse)(nil), | |
| 1264 | 1544 | } | |
| 1265 | 1545 | type x struct{} | |
| 1266 | 1546 | out := protoimpl.TypeBuilder{ | |
| @@ -1268,7 +1548,7 @@ func file_exec_proto_init() { | |||
| 1268 | 1548 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | |
| 1269 | 1549 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)), | |
| 1270 | 1550 | NumEnums: 3, | |
| 1271 | - | NumMessages: 13, | |
| 1551 | + | NumMessages: 16, | |
| 1272 | 1552 | NumExtensions: 0, | |
| 1273 | 1553 | NumServices: 0, | |
| 1274 | 1554 | }, | |
| @@ -203,6 +203,8 @@ type Frame struct { | |||||
| 203 | // *Frame_StepCompleted | 203 | // *Frame_StepCompleted | ||
| 204 | // *Frame_Log | 204 | // *Frame_Log | ||
| 205 | // *Frame_JobResult | 205 | // *Frame_JobResult | ||
| 206 | + | // *Frame_ResolveRequest | |||
| 207 | + | // *Frame_ResolveResponse | |||
| 206 | Body isFrame_Body `protobuf_oneof:"body"` | 208 | Body isFrame_Body `protobuf_oneof:"body"` | ||
| 207 | unknownFields protoimpl.UnknownFields | 209 | unknownFields protoimpl.UnknownFields | ||
| 208 | sizeCache protoimpl.SizeCache | 210 | sizeCache protoimpl.SizeCache | ||
| @@ -326,6 +328,24 @@ func (x *Frame) GetJobResult() *JobResult { | |||||
| 326 | return nil | 328 | return nil | ||
| 327 | } | 329 | } | ||
| 328 | 330 | ||||
| 331 | + | func (x *Frame) GetResolveRequest() *ResolveRequest { | |||
| 332 | + | if x != nil { | |||
| 333 | + | if x, ok := x.Body.(*Frame_ResolveRequest); ok { | |||
| 334 | + | return x.ResolveRequest | |||
| 335 | + | } | |||
| 336 | + | } | |||
| 337 | + | return nil | |||
| 338 | + | } | |||
| 339 | + | ||||
| 340 | + | func (x *Frame) GetResolveResponse() *ResolveResponse { | |||
| 341 | + | if x != nil { | |||
| 342 | + | if x, ok := x.Body.(*Frame_ResolveResponse); ok { | |||
| 343 | + | return x.ResolveResponse | |||
| 344 | + | } | |||
| 345 | + | } | |||
| 346 | + | return nil | |||
| 347 | + | } | |||
| 348 | + | ||||
| 329 | type isFrame_Body interface { | 349 | type isFrame_Body interface { | ||
| 330 | isFrame_Body() | 350 | isFrame_Body() | ||
| 331 | } | 351 | } | ||
| @@ -366,6 +386,14 @@ type Frame_JobResult struct { | |||||
| 366 | JobResult *JobResult `protobuf:"bytes,9,opt,name=job_result,json=jobResult,proto3,oneof"` // executor -> host, before Exit | 386 | JobResult *JobResult `protobuf:"bytes,9,opt,name=job_result,json=jobResult,proto3,oneof"` // executor -> host, before Exit | ||
| 367 | } | 387 | } | ||
| 368 | 388 | ||||
| 389 | + | type Frame_ResolveRequest struct { | |||
| 390 | + | ResolveRequest *ResolveRequest `protobuf:"bytes,10,opt,name=resolve_request,json=resolveRequest,proto3,oneof"` // executor -> host | |||
| 391 | + | } | |||
| 392 | + | ||||
| 393 | + | type Frame_ResolveResponse struct { | |||
| 394 | + | ResolveResponse *ResolveResponse `protobuf:"bytes,11,opt,name=resolve_response,json=resolveResponse,proto3,oneof"` // host -> executor, answers a ResolveRequest | |||
| 395 | + | } | |||
| 396 | + | ||||
| 369 | func (*Frame_Hello) isFrame_Body() {} | 397 | func (*Frame_Hello) isFrame_Body() {} | ||
| 370 | 398 | ||||
| 371 | func (*Frame_Run) isFrame_Body() {} | 399 | func (*Frame_Run) isFrame_Body() {} | ||
| @@ -384,6 +412,10 @@ func (*Frame_Log) isFrame_Body() {} | |||||
| 384 | 412 | ||||
| 385 | func (*Frame_JobResult) isFrame_Body() {} | 413 | func (*Frame_JobResult) isFrame_Body() {} | ||
| 386 | 414 | ||||
| 415 | + | func (*Frame_ResolveRequest) isFrame_Body() {} | |||
| 416 | + | ||||
| 417 | + | func (*Frame_ResolveResponse) isFrame_Body() {} | |||
| 418 | + | ||||
| 387 | type Hello struct { | 419 | type Hello struct { | ||
| 388 | state protoimpl.MessageState `protogen:"open.v1"` | 420 | state protoimpl.MessageState `protogen:"open.v1"` | ||
| 389 | Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor | 421 | Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` // runner version of the executor | ||
| @@ -1090,12 +1122,232 @@ func (x *JobResult) GetRunnerShutdown() bool { | |||||
| 1090 | return false | 1122 | return false | ||
| 1091 | } | 1123 | } | ||
| 1092 | 1124 | ||||
| 1125 | + | // ResolveRequest asks the host to resolve actions with RunnerService. | |||
| 1126 | + | // ResolveActions: the job token stays in the host, so the executor cannot call | |||
| 1127 | + | // it. The executor sends one per level of composite actions (the runner's own | |||
| 1128 | + | // "one more round per level") and waits for the ResolveResponse with the same id. | |||
| 1129 | + | type ResolveRequest struct { | |||
| 1130 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1131 | + | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` | |||
| 1132 | + | Uses []string `protobuf:"bytes,2,rep,name=uses,proto3" json:"uses,omitempty"` // "owner/repo[/path]@ref", at most 100 | |||
| 1133 | + | unknownFields protoimpl.UnknownFields | |||
| 1134 | + | sizeCache protoimpl.SizeCache | |||
| 1135 | + | } | |||
| 1136 | + | ||||
| 1137 | + | func (x *ResolveRequest) Reset() { | |||
| 1138 | + | *x = ResolveRequest{} | |||
| 1139 | + | mi := &file_exec_proto_msgTypes[11] | |||
| 1140 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1141 | + | ms.StoreMessageInfo(mi) | |||
| 1142 | + | } | |||
| 1143 | + | ||||
| 1144 | + | func (x *ResolveRequest) String() string { | |||
| 1145 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1146 | + | } | |||
| 1147 | + | ||||
| 1148 | + | func (*ResolveRequest) ProtoMessage() {} | |||
| 1149 | + | ||||
| 1150 | + | func (x *ResolveRequest) ProtoReflect() protoreflect.Message { | |||
| 1151 | + | mi := &file_exec_proto_msgTypes[11] | |||
| 1152 | + | if x != nil { | |||
| 1153 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1154 | + | if ms.LoadMessageInfo() == nil { | |||
| 1155 | + | ms.StoreMessageInfo(mi) | |||
| 1156 | + | } | |||
| 1157 | + | return ms | |||
| 1158 | + | } | |||
| 1159 | + | return mi.MessageOf(x) | |||
| 1160 | + | } | |||
| 1161 | + | ||||
| 1162 | + | // Deprecated: Use ResolveRequest.ProtoReflect.Descriptor instead. | |||
| 1163 | + | func (*ResolveRequest) Descriptor() ([]byte, []int) { | |||
| 1164 | + | return file_exec_proto_rawDescGZIP(), []int{11} | |||
| 1165 | + | } | |||
| 1166 | + | ||||
| 1167 | + | func (x *ResolveRequest) GetId() int64 { | |||
| 1168 | + | if x != nil { | |||
| 1169 | + | return x.Id | |||
| 1170 | + | } | |||
| 1171 | + | return 0 | |||
| 1172 | + | } | |||
| 1173 | + | ||||
| 1174 | + | func (x *ResolveRequest) GetUses() []string { | |||
| 1175 | + | if x != nil { | |||
| 1176 | + | return x.Uses | |||
| 1177 | + | } | |||
| 1178 | + | return nil | |||
| 1179 | + | } | |||
| 1180 | + | ||||
| 1181 | + | // ResolveResponse answers a ResolveRequest. error is set when the call itself | |||
| 1182 | + | // failed (the executor fails the job); actions has one entry per uses otherwise. | |||
| 1183 | + | type ResolveResponse struct { | |||
| 1184 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1185 | + | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` | |||
| 1186 | + | Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` | |||
| 1187 | + | Actions []*ResolvedAction `protobuf:"bytes,3,rep,name=actions,proto3" json:"actions,omitempty"` | |||
| 1188 | + | unknownFields protoimpl.UnknownFields | |||
| 1189 | + | sizeCache protoimpl.SizeCache | |||
| 1190 | + | } | |||
| 1191 | + | ||||
| 1192 | + | func (x *ResolveResponse) Reset() { | |||
| 1193 | + | *x = ResolveResponse{} | |||
| 1194 | + | mi := &file_exec_proto_msgTypes[12] | |||
| 1195 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1196 | + | ms.StoreMessageInfo(mi) | |||
| 1197 | + | } | |||
| 1198 | + | ||||
| 1199 | + | func (x *ResolveResponse) String() string { | |||
| 1200 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1201 | + | } | |||
| 1202 | + | ||||
| 1203 | + | func (*ResolveResponse) ProtoMessage() {} | |||
| 1204 | + | ||||
| 1205 | + | func (x *ResolveResponse) ProtoReflect() protoreflect.Message { | |||
| 1206 | + | mi := &file_exec_proto_msgTypes[12] | |||
| 1207 | + | if x != nil { | |||
| 1208 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1209 | + | if ms.LoadMessageInfo() == nil { | |||
| 1210 | + | ms.StoreMessageInfo(mi) | |||
| 1211 | + | } | |||
| 1212 | + | return ms | |||
| 1213 | + | } | |||
| 1214 | + | return mi.MessageOf(x) | |||
| 1215 | + | } | |||
| 1216 | + | ||||
| 1217 | + | // Deprecated: Use ResolveResponse.ProtoReflect.Descriptor instead. | |||
| 1218 | + | func (*ResolveResponse) Descriptor() ([]byte, []int) { | |||
| 1219 | + | return file_exec_proto_rawDescGZIP(), []int{12} | |||
| 1220 | + | } | |||
| 1221 | + | ||||
| 1222 | + | func (x *ResolveResponse) GetId() int64 { | |||
| 1223 | + | if x != nil { | |||
| 1224 | + | return x.Id | |||
| 1225 | + | } | |||
| 1226 | + | return 0 | |||
| 1227 | + | } | |||
| 1228 | + | ||||
| 1229 | + | func (x *ResolveResponse) GetError() string { | |||
| 1230 | + | if x != nil { | |||
| 1231 | + | return x.Error | |||
| 1232 | + | } | |||
| 1233 | + | return "" | |||
| 1234 | + | } | |||
| 1235 | + | ||||
| 1236 | + | func (x *ResolveResponse) GetActions() []*ResolvedAction { | |||
| 1237 | + | if x != nil { | |||
| 1238 | + | return x.Actions | |||
| 1239 | + | } | |||
| 1240 | + | return nil | |||
| 1241 | + | } | |||
| 1242 | + | ||||
| 1243 | + | // ResolvedAction mirrors ermite.ResolvedAction (the token is a secret: the | |||
| 1244 | + | // executor masks it). | |||
| 1245 | + | type ResolvedAction struct { | |||
| 1246 | + | state protoimpl.MessageState `protogen:"open.v1"` | |||
| 1247 | + | Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` | |||
| 1248 | + | NameWithOwner string `protobuf:"bytes,2,opt,name=name_with_owner,json=nameWithOwner,proto3" json:"name_with_owner,omitempty"` | |||
| 1249 | + | Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` | |||
| 1250 | + | ResolvedSha string `protobuf:"bytes,4,opt,name=resolved_sha,json=resolvedSha,proto3" json:"resolved_sha,omitempty"` | |||
| 1251 | + | TarballUrl string `protobuf:"bytes,5,opt,name=tarball_url,json=tarballUrl,proto3" json:"tarball_url,omitempty"` | |||
| 1252 | + | Token string `protobuf:"bytes,6,opt,name=token,proto3" json:"token,omitempty"` | |||
| 1253 | + | Source string `protobuf:"bytes,7,opt,name=source,proto3" json:"source,omitempty"` | |||
| 1254 | + | Error string `protobuf:"bytes,8,opt,name=error,proto3" json:"error,omitempty"` | |||
| 1255 | + | unknownFields protoimpl.UnknownFields | |||
| 1256 | + | sizeCache protoimpl.SizeCache | |||
| 1257 | + | } | |||
| 1258 | + | ||||
| 1259 | + | func (x *ResolvedAction) Reset() { | |||
| 1260 | + | *x = ResolvedAction{} | |||
| 1261 | + | mi := &file_exec_proto_msgTypes[13] | |||
| 1262 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1263 | + | ms.StoreMessageInfo(mi) | |||
| 1264 | + | } | |||
| 1265 | + | ||||
| 1266 | + | func (x *ResolvedAction) String() string { | |||
| 1267 | + | return protoimpl.X.MessageStringOf(x) | |||
| 1268 | + | } | |||
| 1269 | + | ||||
| 1270 | + | func (*ResolvedAction) ProtoMessage() {} | |||
| 1271 | + | ||||
| 1272 | + | func (x *ResolvedAction) ProtoReflect() protoreflect.Message { | |||
| 1273 | + | mi := &file_exec_proto_msgTypes[13] | |||
| 1274 | + | if x != nil { | |||
| 1275 | + | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) | |||
| 1276 | + | if ms.LoadMessageInfo() == nil { | |||
| 1277 | + | ms.StoreMessageInfo(mi) | |||
| 1278 | + | } | |||
| 1279 | + | return ms | |||
| 1280 | + | } | |||
| 1281 | + | return mi.MessageOf(x) | |||
| 1282 | + | } | |||
| 1283 | + | ||||
| 1284 | + | // Deprecated: Use ResolvedAction.ProtoReflect.Descriptor instead. | |||
| 1285 | + | func (*ResolvedAction) Descriptor() ([]byte, []int) { | |||
| 1286 | + | return file_exec_proto_rawDescGZIP(), []int{13} | |||
| 1287 | + | } | |||
| 1288 | + | ||||
| 1289 | + | func (x *ResolvedAction) GetRef() string { | |||
| 1290 | + | if x != nil { | |||
| 1291 | + | return x.Ref | |||
| 1292 | + | } | |||
| 1293 | + | return "" | |||
| 1294 | + | } | |||
| 1295 | + | ||||
| 1296 | + | func (x *ResolvedAction) GetNameWithOwner() string { | |||
| 1297 | + | if x != nil { | |||
| 1298 | + | return x.NameWithOwner | |||
| 1299 | + | } | |||
| 1300 | + | return "" | |||
| 1301 | + | } | |||
| 1302 | + | ||||
| 1303 | + | func (x *ResolvedAction) GetPath() string { | |||
| 1304 | + | if x != nil { | |||
| 1305 | + | return x.Path | |||
| 1306 | + | } | |||
| 1307 | + | return "" | |||
| 1308 | + | } | |||
| 1309 | + | ||||
| 1310 | + | func (x *ResolvedAction) GetResolvedSha() string { | |||
| 1311 | + | if x != nil { | |||
| 1312 | + | return x.ResolvedSha | |||
| 1313 | + | } | |||
| 1314 | + | return "" | |||
| 1315 | + | } | |||
| 1316 | + | ||||
| 1317 | + | func (x *ResolvedAction) GetTarballUrl() string { | |||
| 1318 | + | if x != nil { | |||
| 1319 | + | return x.TarballUrl | |||
| 1320 | + | } | |||
| 1321 | + | return "" | |||
| 1322 | + | } | |||
| 1323 | + | ||||
| 1324 | + | func (x *ResolvedAction) GetToken() string { | |||
| 1325 | + | if x != nil { | |||
| 1326 | + | return x.Token | |||
| 1327 | + | } | |||
| 1328 | + | return "" | |||
| 1329 | + | } | |||
| 1330 | + | ||||
| 1331 | + | func (x *ResolvedAction) GetSource() string { | |||
| 1332 | + | if x != nil { | |||
| 1333 | + | return x.Source | |||
| 1334 | + | } | |||
| 1335 | + | return "" | |||
| 1336 | + | } | |||
| 1337 | + | ||||
| 1338 | + | func (x *ResolvedAction) GetError() string { | |||
| 1339 | + | if x != nil { | |||
| 1340 | + | return x.Error | |||
| 1341 | + | } | |||
| 1342 | + | return "" | |||
| 1343 | + | } | |||
| 1344 | + | ||||
| 1093 | var File_exec_proto protoreflect.FileDescriptor | 1345 | var File_exec_proto protoreflect.FileDescriptor | ||
| 1094 | 1346 | ||||
| 1095 | const file_exec_proto_rawDesc = "" + | 1347 | const file_exec_proto_rawDesc = "" + | ||
| 1096 | "\n" + | 1348 | "\n" + | ||
| 1097 | "\n" + | 1349 | "\n" + | ||
| 1098 | - | "exec.proto\x12\x15ermite.runner.exec.v1\"\xa5\x04\n" + | 1350 | + | "exec.proto\x12\x15ermite.runner.exec.v1\"\xcc\x05\n" + |
| 1099 | "\x05Frame\x124\n" + | 1351 | "\x05Frame\x124\n" + | ||
| 1100 | "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" + | 1352 | "\x05hello\x18\x01 \x01(\v2\x1c.ermite.runner.exec.v1.HelloH\x00R\x05hello\x12.\n" + | ||
| 1101 | "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" + | 1353 | "\x03run\x18\x02 \x01(\v2\x1a.ermite.runner.exec.v1.RunH\x00R\x03run\x127\n" + | ||
| @@ -1106,7 +1358,10 @@ const file_exec_proto_rawDesc = "" + | |||||
| 1106 | "\x0estep_completed\x18\a \x01(\v2$.ermite.runner.exec.v1.StepCompletedH\x00R\rstepCompleted\x12.\n" + | 1358 | "\x0estep_completed\x18\a \x01(\v2$.ermite.runner.exec.v1.StepCompletedH\x00R\rstepCompleted\x12.\n" + | ||
| 1107 | "\x03log\x18\b \x01(\v2\x1a.ermite.runner.exec.v1.LogH\x00R\x03log\x12A\n" + | 1359 | "\x03log\x18\b \x01(\v2\x1a.ermite.runner.exec.v1.LogH\x00R\x03log\x12A\n" + | ||
| 1108 | "\n" + | 1360 | "\n" + | ||
| 1109 | - | "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResultB\x06\n" + | 1361 | + | "job_result\x18\t \x01(\v2 .ermite.runner.exec.v1.JobResultH\x00R\tjobResult\x12P\n" + |
| 1362 | + | "\x0fresolve_request\x18\n" + | |||
| 1363 | + | " \x01(\v2%.ermite.runner.exec.v1.ResolveRequestH\x00R\x0eresolveRequest\x12S\n" + | |||
| 1364 | + | "\x10resolve_response\x18\v \x01(\v2&.ermite.runner.exec.v1.ResolveResponseH\x00R\x0fresolveResponseB\x06\n" + | |||
| 1110 | "\x04body\"W\n" + | 1365 | "\x04body\"W\n" + | ||
| 1111 | "\x05Hello\x12\x18\n" + | 1366 | "\x05Hello\x12\x18\n" + | ||
| 1112 | "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + | 1367 | "\aversion\x18\x01 \x01(\tR\aversion\x12\x18\n" + | ||
| @@ -1170,7 +1425,24 @@ const file_exec_proto_rawDesc = "" + | |||||
| 1170 | "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x1a:\n" + | 1425 | "\x0frunner_shutdown\x18\x04 \x01(\bR\x0erunnerShutdown\x1a:\n" + | ||
| 1171 | "\fOutputsEntry\x12\x10\n" + | 1426 | "\fOutputsEntry\x12\x10\n" + | ||
| 1172 | "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + | 1427 | "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + | ||
| 1173 | - | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01*Y\n" + | 1428 | + | "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"4\n" + |
| 1429 | + | "\x0eResolveRequest\x12\x0e\n" + | |||
| 1430 | + | "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" + | |||
| 1431 | + | "\x04uses\x18\x02 \x03(\tR\x04uses\"x\n" + | |||
| 1432 | + | "\x0fResolveResponse\x12\x0e\n" + | |||
| 1433 | + | "\x02id\x18\x01 \x01(\x03R\x02id\x12\x14\n" + | |||
| 1434 | + | "\x05error\x18\x02 \x01(\tR\x05error\x12?\n" + | |||
| 1435 | + | "\aactions\x18\x03 \x03(\v2%.ermite.runner.exec.v1.ResolvedActionR\aactions\"\xe6\x01\n" + | |||
| 1436 | + | "\x0eResolvedAction\x12\x10\n" + | |||
| 1437 | + | "\x03ref\x18\x01 \x01(\tR\x03ref\x12&\n" + | |||
| 1438 | + | "\x0fname_with_owner\x18\x02 \x01(\tR\rnameWithOwner\x12\x12\n" + | |||
| 1439 | + | "\x04path\x18\x03 \x01(\tR\x04path\x12!\n" + | |||
| 1440 | + | "\fresolved_sha\x18\x04 \x01(\tR\vresolvedSha\x12\x1f\n" + | |||
| 1441 | + | "\vtarball_url\x18\x05 \x01(\tR\n" + | |||
| 1442 | + | "tarballUrl\x12\x14\n" + | |||
| 1443 | + | "\x05token\x18\x06 \x01(\tR\x05token\x12\x16\n" + | |||
| 1444 | + | "\x06source\x18\a \x01(\tR\x06source\x12\x14\n" + | |||
| 1445 | + | "\x05error\x18\b \x01(\tR\x05error*Y\n" + | |||
| 1174 | "\n" + | 1446 | "\n" + | ||
| 1175 | "StreamKind\x12\x1b\n" + | 1447 | "StreamKind\x12\x1b\n" + | ||
| 1176 | "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" + | 1448 | "\x17STREAM_KIND_UNSPECIFIED\x10\x00\x12\x16\n" + | ||
| @@ -1203,24 +1475,27 @@ func file_exec_proto_rawDescGZIP() []byte { | |||||
| 1203 | } | 1475 | } | ||
| 1204 | 1476 | ||||
| 1205 | var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 3) | 1477 | var file_exec_proto_enumTypes = make([]protoimpl.EnumInfo, 3) | ||
| 1206 | - | var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 13) | 1478 | + | var file_exec_proto_msgTypes = make([]protoimpl.MessageInfo, 16) |
| 1207 | var file_exec_proto_goTypes = []any{ | 1479 | var file_exec_proto_goTypes = []any{ | ||
| 1208 | - | (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind | 1480 | + | (StreamKind)(0), // 0: ermite.runner.exec.v1.StreamKind |
| 1209 | - | (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind | 1481 | + | (CancelKind)(0), // 1: ermite.runner.exec.v1.CancelKind |
| 1210 | - | (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion | 1482 | + | (StepConclusion)(0), // 2: ermite.runner.exec.v1.StepConclusion |
| 1211 | - | (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame | 1483 | + | (*Frame)(nil), // 3: ermite.runner.exec.v1.Frame |
| 1212 | - | (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello | 1484 | + | (*Hello)(nil), // 4: ermite.runner.exec.v1.Hello |
| 1213 | - | (*Run)(nil), // 5: ermite.runner.exec.v1.Run | 1485 | + | (*Run)(nil), // 5: ermite.runner.exec.v1.Run |
| 1214 | - | (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec | 1486 | + | (*JobSpec)(nil), // 6: ermite.runner.exec.v1.JobSpec |
| 1215 | - | (*Output)(nil), // 7: ermite.runner.exec.v1.Output | 1487 | + | (*Output)(nil), // 7: ermite.runner.exec.v1.Output |
| 1216 | - | (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit | 1488 | + | (*Exit)(nil), // 8: ermite.runner.exec.v1.Exit |
| 1217 | - | (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel | 1489 | + | (*Cancel)(nil), // 9: ermite.runner.exec.v1.Cancel |
| 1218 | - | (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted | 1490 | + | (*StepStarted)(nil), // 10: ermite.runner.exec.v1.StepStarted |
| 1219 | - | (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted | 1491 | + | (*StepCompleted)(nil), // 11: ermite.runner.exec.v1.StepCompleted |
| 1220 | - | (*Log)(nil), // 12: ermite.runner.exec.v1.Log | 1492 | + | (*Log)(nil), // 12: ermite.runner.exec.v1.Log |
| 1221 | - | (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult | 1493 | + | (*JobResult)(nil), // 13: ermite.runner.exec.v1.JobResult |
| 1222 | - | nil, // 14: ermite.runner.exec.v1.Run.EnvEntry | 1494 | + | (*ResolveRequest)(nil), // 14: ermite.runner.exec.v1.ResolveRequest |
| 1223 | - | nil, // 15: ermite.runner.exec.v1.JobResult.OutputsEntry | 1495 | + | (*ResolveResponse)(nil), // 15: ermite.runner.exec.v1.ResolveResponse |
| 1496 | + | (*ResolvedAction)(nil), // 16: ermite.runner.exec.v1.ResolvedAction | |||
| 1497 | + | nil, // 17: ermite.runner.exec.v1.Run.EnvEntry | |||
| 1498 | + | nil, // 18: ermite.runner.exec.v1.JobResult.OutputsEntry | |||
| 1224 | } | 1499 | } | ||
| 1225 | var file_exec_proto_depIdxs = []int32{ | 1500 | var file_exec_proto_depIdxs = []int32{ | ||
| 1226 | 4, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello | 1501 | 4, // 0: ermite.runner.exec.v1.Frame.hello:type_name -> ermite.runner.exec.v1.Hello | ||
| @@ -1232,18 +1507,21 @@ var file_exec_proto_depIdxs = []int32{ | |||||
| 1232 | 11, // 6: ermite.runner.exec.v1.Frame.step_completed:type_name -> ermite.runner.exec.v1.StepCompleted | 1507 | 11, // 6: ermite.runner.exec.v1.Frame.step_completed:type_name -> ermite.runner.exec.v1.StepCompleted | ||
| 1233 | 12, // 7: ermite.runner.exec.v1.Frame.log:type_name -> ermite.runner.exec.v1.Log | 1508 | 12, // 7: ermite.runner.exec.v1.Frame.log:type_name -> ermite.runner.exec.v1.Log | ||
| 1234 | 13, // 8: ermite.runner.exec.v1.Frame.job_result:type_name -> ermite.runner.exec.v1.JobResult | 1509 | 13, // 8: ermite.runner.exec.v1.Frame.job_result:type_name -> ermite.runner.exec.v1.JobResult | ||
| 1235 | - | 14, // 9: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry | 1510 | + | 14, // 9: ermite.runner.exec.v1.Frame.resolve_request:type_name -> ermite.runner.exec.v1.ResolveRequest |
| 1236 | - | 6, // 10: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec | 1511 | + | 15, // 10: ermite.runner.exec.v1.Frame.resolve_response:type_name -> ermite.runner.exec.v1.ResolveResponse |
| 1237 | - | 0, // 11: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind | 1512 | + | 17, // 11: ermite.runner.exec.v1.Run.env:type_name -> ermite.runner.exec.v1.Run.EnvEntry |
| 1238 | - | 1, // 12: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind | 1513 | + | 6, // 12: ermite.runner.exec.v1.Run.job:type_name -> ermite.runner.exec.v1.JobSpec |
| 1239 | - | 2, // 13: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | 1514 | + | 0, // 13: ermite.runner.exec.v1.Output.stream:type_name -> ermite.runner.exec.v1.StreamKind |
| 1240 | - | 2, // 14: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion | 1515 | + | 1, // 14: ermite.runner.exec.v1.Cancel.kind:type_name -> ermite.runner.exec.v1.CancelKind |
| 1241 | - | 15, // 15: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry | 1516 | + | 2, // 15: ermite.runner.exec.v1.StepCompleted.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion |
| 1242 | - | 16, // [16:16] is the sub-list for method output_type | 1517 | + | 2, // 16: ermite.runner.exec.v1.JobResult.conclusion:type_name -> ermite.runner.exec.v1.StepConclusion |
| 1243 | - | 16, // [16:16] is the sub-list for method input_type | 1518 | + | 18, // 17: ermite.runner.exec.v1.JobResult.outputs:type_name -> ermite.runner.exec.v1.JobResult.OutputsEntry |
| 1244 | - | 16, // [16:16] is the sub-list for extension type_name | 1519 | + | 16, // 18: ermite.runner.exec.v1.ResolveResponse.actions:type_name -> ermite.runner.exec.v1.ResolvedAction |
| 1245 | - | 16, // [16:16] is the sub-list for extension extendee | 1520 | + | 19, // [19:19] is the sub-list for method output_type |
| 1246 | - | 0, // [0:16] is the sub-list for field type_name | 1521 | + | 19, // [19:19] is the sub-list for method input_type |
| 1522 | + | 19, // [19:19] is the sub-list for extension type_name | |||
| 1523 | + | 19, // [19:19] is the sub-list for extension extendee | |||
| 1524 | + | 0, // [0:19] is the sub-list for field type_name | |||
| 1247 | } | 1525 | } | ||
| 1248 | 1526 | ||||
| 1249 | func init() { file_exec_proto_init() } | 1527 | func init() { file_exec_proto_init() } | ||
| @@ -1261,6 +1539,8 @@ func file_exec_proto_init() { | |||||
| 1261 | (*Frame_StepCompleted)(nil), | 1539 | (*Frame_StepCompleted)(nil), | ||
| 1262 | (*Frame_Log)(nil), | 1540 | (*Frame_Log)(nil), | ||
| 1263 | (*Frame_JobResult)(nil), | 1541 | (*Frame_JobResult)(nil), | ||
| 1542 | + | (*Frame_ResolveRequest)(nil), | |||
| 1543 | + | (*Frame_ResolveResponse)(nil), | |||
| 1264 | } | 1544 | } | ||
| 1265 | type x struct{} | 1545 | type x struct{} | ||
| 1266 | out := protoimpl.TypeBuilder{ | 1546 | out := protoimpl.TypeBuilder{ | ||
| @@ -1268,7 +1548,7 @@ func file_exec_proto_init() { | |||||
| 1268 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | 1548 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), | ||
| 1269 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)), | 1549 | RawDescriptor: unsafe.Slice(unsafe.StringData(file_exec_proto_rawDesc), len(file_exec_proto_rawDesc)), | ||
| 1270 | NumEnums: 3, | 1550 | NumEnums: 3, | ||
| 1271 | - | NumMessages: 13, | 1551 | + | NumMessages: 16, |
| 1272 | NumExtensions: 0, | 1552 | NumExtensions: 0, | ||
| 1273 | NumServices: 0, | 1553 | NumServices: 0, | ||
| 1274 | }, | 1554 | }, | ||
M internal/execstream/frame.go +14 -0
| @@ -115,6 +115,10 @@ type JobHandler struct { | |||
| 115 | 115 | StepCompleted func(*execpb.StepCompleted) | |
| 116 | 116 | Log func(*execpb.Log) | |
| 117 | 117 | JobResult func(*execpb.JobResult) | |
| 118 | + | // Resolve answers the executor's ResolveRequest frames. It is called in its | |
| 119 | + | // own goroutine (the call goes to the server), the answer is sent as a | |
| 120 | + | // ResolveResponse frame carrying the id of the request. | |
| 121 | + | Resolve func(*execpb.ResolveRequest) *execpb.ResolveResponse | |
| 118 | 122 | } | |
| 119 | 123 | ||
| 120 | 124 | // DriveJob is Drive for a session that carries a job: besides Output frames it | |
| @@ -179,6 +183,16 @@ func DriveJob(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, h | |||
| 179 | 183 | if h.JobResult != nil { | |
| 180 | 184 | h.JobResult(b.JobResult) | |
| 181 | 185 | } | |
| 186 | + | case *execpb.Frame_ResolveRequest: | |
| 187 | + | req := b.ResolveRequest | |
| 188 | + | go func() { | |
| 189 | + | resp := &execpb.ResolveResponse{Id: req.Id, Error: "the host cannot resolve actions"} | |
| 190 | + | if h.Resolve != nil { | |
| 191 | + | resp = h.Resolve(req) | |
| 192 | + | resp.Id = req.Id | |
| 193 | + | } | |
| 194 | + | _ = c.Send(&execpb.Frame{Body: &execpb.Frame_ResolveResponse{ResolveResponse: resp}}) | |
| 195 | + | }() | |
| 182 | 196 | case *execpb.Frame_Exit: | |
| 183 | 197 | return hello, b.Exit, nil | |
| 184 | 198 | } | |
| @@ -115,6 +115,10 @@ type JobHandler struct { | |||||
| 115 | StepCompleted func(*execpb.StepCompleted) | 115 | StepCompleted func(*execpb.StepCompleted) | ||
| 116 | Log func(*execpb.Log) | 116 | Log func(*execpb.Log) | ||
| 117 | JobResult func(*execpb.JobResult) | 117 | JobResult func(*execpb.JobResult) | ||
| 118 | + | // Resolve answers the executor's ResolveRequest frames. It is called in its | |||
| 119 | + | // own goroutine (the call goes to the server), the answer is sent as a | |||
| 120 | + | // ResolveResponse frame carrying the id of the request. | |||
| 121 | + | Resolve func(*execpb.ResolveRequest) *execpb.ResolveResponse | |||
| 118 | } | 122 | } | ||
| 119 | 123 | ||||
| 120 | // DriveJob is Drive for a session that carries a job: besides Output frames it | 124 | // DriveJob is Drive for a session that carries a job: besides Output frames it | ||
| @@ -179,6 +183,16 @@ func DriveJob(rw io.ReadWriter, run *execpb.Run, cancel <-chan *execpb.Cancel, h | |||||
| 179 | if h.JobResult != nil { | 183 | if h.JobResult != nil { | ||
| 180 | h.JobResult(b.JobResult) | 184 | h.JobResult(b.JobResult) | ||
| 181 | } | 185 | } | ||
| 186 | + | case *execpb.Frame_ResolveRequest: | |||
| 187 | + | req := b.ResolveRequest | |||
| 188 | + | go func() { | |||
| 189 | + | resp := &execpb.ResolveResponse{Id: req.Id, Error: "the host cannot resolve actions"} | |||
| 190 | + | if h.Resolve != nil { | |||
| 191 | + | resp = h.Resolve(req) | |||
| 192 | + | resp.Id = req.Id | |||
| 193 | + | } | |||
| 194 | + | _ = c.Send(&execpb.Frame{Body: &execpb.Frame_ResolveResponse{ResolveResponse: resp}}) | |||
| 195 | + | }() | |||
| 182 | case *execpb.Frame_Exit: | 196 | case *execpb.Frame_Exit: | ||
| 183 | return hello, b.Exit, nil | 197 | return hello, b.Exit, nil | ||
| 184 | } | 198 | } | ||
A internal/executor/actions_types.go +134 -0
| @@ -0,0 +1,134 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package executor | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "net/http" | |
| 8 | + | "strings" | |
| 9 | + | ||
| 10 | + | "bnema.dev/ermite-actions/action" | |
| 11 | + | "bnema.dev/ermite-actions/workflow" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // ResolvedAction is where to download one action (ermite.ResolvedAction, see | |
| 15 | + | // the host). The Token is a secret: the executor masks it. | |
| 16 | + | type ResolvedAction struct { | |
| 17 | + | Ref, NameWithOwner, Path, ResolvedSHA, TarballURL, Token, Error string | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // Resolver resolves "owner/repo[/path]@ref" values of uses with the server. It | |
| 21 | + | // is the host's call (the job token stays out of the sandbox); the result has | |
| 22 | + | // one entry per value, in order. | |
| 23 | + | type Resolver func(ctx context.Context, uses []string) ([]ResolvedAction, error) | |
| 24 | + | ||
| 25 | + | // ActionsConfig is what the executor needs to run actions. | |
| 26 | + | type ActionsConfig struct { | |
| 27 | + | // Resolver is required to run actions from repositories; without it a job | |
| 28 | + | // that uses one fails "Set up job". | |
| 29 | + | Resolver Resolver | |
| 30 | + | // HTTPClient downloads the archives; nil means a client with the default | |
| 31 | + | // transport. It must not follow a redirect to another host with the | |
| 32 | + | // credentials (net/http does not). | |
| 33 | + | HTTPClient *http.Client | |
| 34 | + | // ExternalsDir is where Node.js lives (<dir>/node20/bin/node); empty means | |
| 35 | + | // /home/runner/externals. | |
| 36 | + | ExternalsDir string | |
| 37 | + | // DockerPath is the docker CLI; empty means the one found in PATH. | |
| 38 | + | DockerPath string | |
| 39 | + | // Node24ByDefault is the server flag actions.runner.usenode24bydefault | |
| 40 | + | // (HandlerFactory.cs, NodeUtil.DetermineActionsNodeVersion): ermite has no | |
| 41 | + | // feature flags, so the default is pinned here. | |
| 42 | + | Node24ByDefault bool | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | const defaultExternalsDir = "/home/runner/externals" | |
| 46 | + | ||
| 47 | + | // stage is ActionRunStage. | |
| 48 | + | type stage int | |
| 49 | + | ||
| 50 | + | const ( | |
| 51 | + | stageMain stage = iota | |
| 52 | + | stagePre | |
| 53 | + | stagePost | |
| 54 | + | ) | |
| 55 | + | ||
| 56 | + | // compositeMaxDepth is Constants.CompositeActionsMaxDepth | |
| 57 | + | // (Runner.Common/Constants.cs:46). | |
| 58 | + | const compositeMaxDepth = 9 | |
| 59 | + | ||
| 60 | + | // actionDef is a loaded action: its directory and manifest (ActionManager. | |
| 61 | + | // LoadAction's Definition). A docker:// step has no manifest and no directory. | |
| 62 | + | type actionDef struct { | |
| 63 | + | uses action.Uses | |
| 64 | + | // ref is "Name[/Path]@Ref" for repository actions, the path for local ones. | |
| 65 | + | ref string | |
| 66 | + | dir string | |
| 67 | + | act *action.Action // nil for docker:// and Dockerfile-only actions | |
| 68 | + | // image is what a container action runs: "docker://x" steps and docker:// | |
| 69 | + | // manifests give the image to pull, Dockerfile ones the path of the file. | |
| 70 | + | image string | |
| 71 | + | dockerfile bool | |
| 72 | + | // children are the loaded actions of a composite's steps, by step. | |
| 73 | + | children map[*workflow.Step]*actionDef | |
| 74 | + | } | |
| 75 | + | ||
| 76 | + | func (d *actionDef) runs() *action.Runs { | |
| 77 | + | if d.act == nil { | |
| 78 | + | return nil | |
| 79 | + | } | |
| 80 | + | return d.act.Runs | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // hasPre and hasPost are ActionExecutionData.HasPre/HasPost. | |
| 84 | + | func (d *actionDef) hasPre() bool { | |
| 85 | + | r := d.runs() | |
| 86 | + | switch { | |
| 87 | + | case r == nil: | |
| 88 | + | return false | |
| 89 | + | case r.Kind == action.RunsComposite: | |
| 90 | + | return d.anyChild(func(c *actionDef) bool { return c.hasPre() && !c.local() }) | |
| 91 | + | } | |
| 92 | + | return r.HasPre() | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func (d *actionDef) hasPost() bool { | |
| 96 | + | r := d.runs() | |
| 97 | + | switch { | |
| 98 | + | case r == nil: | |
| 99 | + | return false | |
| 100 | + | case r.Kind == action.RunsComposite: | |
| 101 | + | return d.anyChild(func(c *actionDef) bool { return c.local() || c.hasPost() }) | |
| 102 | + | } | |
| 103 | + | return r.HasPost() | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | func (d *actionDef) anyChild(f func(*actionDef) bool) bool { | |
| 107 | + | for _, c := range d.children { | |
| 108 | + | if c != nil && f(c) { | |
| 109 | + | return true | |
| 110 | + | } | |
| 111 | + | } | |
| 112 | + | return false | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | func (d *actionDef) local() bool { return d.uses.Kind == action.UsesLocal } | |
| 116 | + | ||
| 117 | + | // preIf and postIf are InitCondition and CleanupCondition: always() unless the | |
| 118 | + | // manifest says otherwise (action.Runs keeps them as written). | |
| 119 | + | func (d *actionDef) preIf() string { return condOrAlways(d.runs(), true) } | |
| 120 | + | func (d *actionDef) postIf() string { return condOrAlways(d.runs(), false) } | |
| 121 | + | ||
| 122 | + | func condOrAlways(r *action.Runs, pre bool) string { | |
| 123 | + | if r == nil || r.Kind == action.RunsComposite { | |
| 124 | + | return "always()" | |
| 125 | + | } | |
| 126 | + | c := r.PostIf | |
| 127 | + | if pre { | |
| 128 | + | c = r.PreIf | |
| 129 | + | } | |
| 130 | + | if strings.TrimSpace(c) == "" { | |
| 131 | + | return "always()" | |
| 132 | + | } | |
| 133 | + | return c | |
| 134 | + | } | |
| @@ -0,0 +1,134 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | ||||
| 3 | + | package executor | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "net/http" | |||
| 8 | + | "strings" | |||
| 9 | + | ||||
| 10 | + | "bnema.dev/ermite-actions/action" | |||
| 11 | + | "bnema.dev/ermite-actions/workflow" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // ResolvedAction is where to download one action (ermite.ResolvedAction, see | |||
| 15 | + | // the host). The Token is a secret: the executor masks it. | |||
| 16 | + | type ResolvedAction struct { | |||
| 17 | + | Ref, NameWithOwner, Path, ResolvedSHA, TarballURL, Token, Error string | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | // Resolver resolves "owner/repo[/path]@ref" values of uses with the server. It | |||
| 21 | + | // is the host's call (the job token stays out of the sandbox); the result has | |||
| 22 | + | // one entry per value, in order. | |||
| 23 | + | type Resolver func(ctx context.Context, uses []string) ([]ResolvedAction, error) | |||
| 24 | + | ||||
| 25 | + | // ActionsConfig is what the executor needs to run actions. | |||
| 26 | + | type ActionsConfig struct { | |||
| 27 | + | // Resolver is required to run actions from repositories; without it a job | |||
| 28 | + | // that uses one fails "Set up job". | |||
| 29 | + | Resolver Resolver | |||
| 30 | + | // HTTPClient downloads the archives; nil means a client with the default | |||
| 31 | + | // transport. It must not follow a redirect to another host with the | |||
| 32 | + | // credentials (net/http does not). | |||
| 33 | + | HTTPClient *http.Client | |||
| 34 | + | // ExternalsDir is where Node.js lives (<dir>/node20/bin/node); empty means | |||
| 35 | + | // /home/runner/externals. | |||
| 36 | + | ExternalsDir string | |||
| 37 | + | // DockerPath is the docker CLI; empty means the one found in PATH. | |||
| 38 | + | DockerPath string | |||
| 39 | + | // Node24ByDefault is the server flag actions.runner.usenode24bydefault | |||
| 40 | + | // (HandlerFactory.cs, NodeUtil.DetermineActionsNodeVersion): ermite has no | |||
| 41 | + | // feature flags, so the default is pinned here. | |||
| 42 | + | Node24ByDefault bool | |||
| 43 | + | } | |||
| 44 | + | ||||
| 45 | + | const defaultExternalsDir = "/home/runner/externals" | |||
| 46 | + | ||||
| 47 | + | // stage is ActionRunStage. | |||
| 48 | + | type stage int | |||
| 49 | + | ||||
| 50 | + | const ( | |||
| 51 | + | stageMain stage = iota | |||
| 52 | + | stagePre | |||
| 53 | + | stagePost | |||
| 54 | + | ) | |||
| 55 | + | ||||
| 56 | + | // compositeMaxDepth is Constants.CompositeActionsMaxDepth | |||
| 57 | + | // (Runner.Common/Constants.cs:46). | |||
| 58 | + | const compositeMaxDepth = 9 | |||
| 59 | + | ||||
| 60 | + | // actionDef is a loaded action: its directory and manifest (ActionManager. | |||
| 61 | + | // LoadAction's Definition). A docker:// step has no manifest and no directory. | |||
| 62 | + | type actionDef struct { | |||
| 63 | + | uses action.Uses | |||
| 64 | + | // ref is "Name[/Path]@Ref" for repository actions, the path for local ones. | |||
| 65 | + | ref string | |||
| 66 | + | dir string | |||
| 67 | + | act *action.Action // nil for docker:// and Dockerfile-only actions | |||
| 68 | + | // image is what a container action runs: "docker://x" steps and docker:// | |||
| 69 | + | // manifests give the image to pull, Dockerfile ones the path of the file. | |||
| 70 | + | image string | |||
| 71 | + | dockerfile bool | |||
| 72 | + | // children are the loaded actions of a composite's steps, by step. | |||
| 73 | + | children map[*workflow.Step]*actionDef | |||
| 74 | + | } | |||
| 75 | + | ||||
| 76 | + | func (d *actionDef) runs() *action.Runs { | |||
| 77 | + | if d.act == nil { | |||
| 78 | + | return nil | |||
| 79 | + | } | |||
| 80 | + | return d.act.Runs | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // hasPre and hasPost are ActionExecutionData.HasPre/HasPost. | |||
| 84 | + | func (d *actionDef) hasPre() bool { | |||
| 85 | + | r := d.runs() | |||
| 86 | + | switch { | |||
| 87 | + | case r == nil: | |||
| 88 | + | return false | |||
| 89 | + | case r.Kind == action.RunsComposite: | |||
| 90 | + | return d.anyChild(func(c *actionDef) bool { return c.hasPre() && !c.local() }) | |||
| 91 | + | } | |||
| 92 | + | return r.HasPre() | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func (d *actionDef) hasPost() bool { | |||
| 96 | + | r := d.runs() | |||
| 97 | + | switch { | |||
| 98 | + | case r == nil: | |||
| 99 | + | return false | |||
| 100 | + | case r.Kind == action.RunsComposite: | |||
| 101 | + | return d.anyChild(func(c *actionDef) bool { return c.local() || c.hasPost() }) | |||
| 102 | + | } | |||
| 103 | + | return r.HasPost() | |||
| 104 | + | } | |||
| 105 | + | ||||
| 106 | + | func (d *actionDef) anyChild(f func(*actionDef) bool) bool { | |||
| 107 | + | for _, c := range d.children { | |||
| 108 | + | if c != nil && f(c) { | |||
| 109 | + | return true | |||
| 110 | + | } | |||
| 111 | + | } | |||
| 112 | + | return false | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | func (d *actionDef) local() bool { return d.uses.Kind == action.UsesLocal } | |||
| 116 | + | ||||
| 117 | + | // preIf and postIf are InitCondition and CleanupCondition: always() unless the | |||
| 118 | + | // manifest says otherwise (action.Runs keeps them as written). | |||
| 119 | + | func (d *actionDef) preIf() string { return condOrAlways(d.runs(), true) } | |||
| 120 | + | func (d *actionDef) postIf() string { return condOrAlways(d.runs(), false) } | |||
| 121 | + | ||||
| 122 | + | func condOrAlways(r *action.Runs, pre bool) string { | |||
| 123 | + | if r == nil || r.Kind == action.RunsComposite { | |||
| 124 | + | return "always()" | |||
| 125 | + | } | |||
| 126 | + | c := r.PostIf | |||
| 127 | + | if pre { | |||
| 128 | + | c = r.PreIf | |||
| 129 | + | } | |||
| 130 | + | if strings.TrimSpace(c) == "" { | |||
| 131 | + | return "always()" | |||
| 132 | + | } | |||
| 133 | + | return c | |||
| 134 | + | } | |||
M internal/executor/contexts.go +3 -0
| @@ -130,6 +130,8 @@ type Layout struct { | |||
| 130 | 130 | RunnerWorkspace string | |
| 131 | 131 | // Workspace is GITHUB_WORKSPACE: <WorkRoot>/<repo>/<repo>. | |
| 132 | 132 | Workspace string | |
| 133 | + | // Actions is where the actions of the job are downloaded: <WorkRoot>/_actions. | |
| 134 | + | Actions string | |
| 133 | 135 | } | |
| 134 | 136 | ||
| 135 | 137 | // DefaultToolCache is RUNNER_TOOL_CACHE on the ermite image. | |
| @@ -145,6 +147,7 @@ func NewLayout(home, repo, toolCache string) Layout { | |||
| 145 | 147 | return Layout{ | |
| 146 | 148 | Home: home, WorkRoot: work, Temp: filepath.Join(work, "_temp"), ToolCache: toolCache, | |
| 147 | 149 | RunnerWorkspace: filepath.Join(work, repo), Workspace: filepath.Join(work, repo, repo), | |
| 150 | + | Actions: filepath.Join(work, "_actions"), | |
| 148 | 151 | } | |
| 149 | 152 | } | |
| 150 | 153 | ||
| @@ -130,6 +130,8 @@ type Layout struct { | |||||
| 130 | RunnerWorkspace string | 130 | RunnerWorkspace string | ||
| 131 | // Workspace is GITHUB_WORKSPACE: <WorkRoot>/<repo>/<repo>. | 131 | // Workspace is GITHUB_WORKSPACE: <WorkRoot>/<repo>/<repo>. | ||
| 132 | Workspace string | 132 | Workspace string | ||
| 133 | + | // Actions is where the actions of the job are downloaded: <WorkRoot>/_actions. | |||
| 134 | + | Actions string | |||
| 133 | } | 135 | } | ||
| 134 | 136 | ||||
| 135 | // DefaultToolCache is RUNNER_TOOL_CACHE on the ermite image. | 137 | // DefaultToolCache is RUNNER_TOOL_CACHE on the ermite image. | ||
| @@ -145,6 +147,7 @@ func NewLayout(home, repo, toolCache string) Layout { | |||||
| 145 | return Layout{ | 147 | return Layout{ | ||
| 146 | Home: home, WorkRoot: work, Temp: filepath.Join(work, "_temp"), ToolCache: toolCache, | 148 | Home: home, WorkRoot: work, Temp: filepath.Join(work, "_temp"), ToolCache: toolCache, | ||
| 147 | RunnerWorkspace: filepath.Join(work, repo), Workspace: filepath.Join(work, repo, repo), | 149 | RunnerWorkspace: filepath.Join(work, repo), Workspace: filepath.Join(work, repo, repo), | ||
| 150 | + | Actions: filepath.Join(work, "_actions"), | |||
| 148 | } | 151 | } | ||
| 149 | } | 152 | } | ||
| 150 | 153 | ||||
M internal/executor/job.go +3 -0
| @@ -75,6 +75,9 @@ type Config struct { | |||
| 75 | 75 | BaseEnv []string | |
| 76 | 76 | // Debug turns ##[debug] lines on (ACTIONS_STEP_DEBUG). | |
| 77 | 77 | Debug bool | |
| 78 | + | // Actions configures the actions of the job (resolution, downloads, Node.js, | |
| 79 | + | // docker). | |
| 80 | + | Actions ActionsConfig | |
| 78 | 81 | ||
| 79 | 82 | Reporter Reporter | |
| 80 | 83 | // Log receives the runner's own diagnostics (not the job log); the zero value | |
| @@ -75,6 +75,9 @@ type Config struct { | |||||
| 75 | BaseEnv []string | 75 | BaseEnv []string | ||
| 76 | // Debug turns ##[debug] lines on (ACTIONS_STEP_DEBUG). | 76 | // Debug turns ##[debug] lines on (ACTIONS_STEP_DEBUG). | ||
| 77 | Debug bool | 77 | Debug bool | ||
| 78 | + | // Actions configures the actions of the job (resolution, downloads, Node.js, | |||
| 79 | + | // docker). | |||
| 80 | + | Actions ActionsConfig | |||
| 78 | 81 | ||||
| 79 | Reporter Reporter | 82 | Reporter Reporter | ||
| 80 | // Log receives the runner's own diagnostics (not the job log); the zero value | 83 | // Log receives the runner's own diagnostics (not the job log); the zero value | ||
M internal/guest/jobexec.go +7 -3
| @@ -166,6 +166,7 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||
| 166 | 166 | } | |
| 167 | 167 | canceller := executor.NewCanceller() | |
| 168 | 168 | rep := &frameReporter{conn: conn, failed: make(chan struct{})} | |
| 169 | + | res := newResolver(conn) | |
| 169 | 170 | ||
| 170 | 171 | // Frames from the host while the job runs: a Cancel stops the job; a lost | |
| 171 | 172 | // host does too, like a runner shutdown. The reader ends with the stream: the | |
| @@ -183,6 +184,9 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||
| 183 | 184 | if c := f.GetCancel(); c != nil { | |
| 184 | 185 | canceller.Cancel(cancelKind(c.GetKind())) | |
| 185 | 186 | } | |
| 187 | + | if r := f.GetResolveResponse(); r != nil { | |
| 188 | + | res.deliver(r) | |
| 189 | + | } | |
| 186 | 190 | } | |
| 187 | 191 | }() | |
| 188 | 192 | // The executor's own context (SIGTERM forwarded by init: the sandbox is | |
| @@ -199,17 +203,17 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||
| 199 | 203 | }() | |
| 200 | 204 | ||
| 201 | 205 | env := spec.GetRunnerEnvironment() | |
| 202 | - | res := executor.Run(canceller, executor.Config{ | |
| 206 | + | result := executor.Run(canceller, executor.Config{ | |
| 203 | 207 | WorkflowPath: spec.GetWorkflowPath(), WorkflowYAML: []byte(spec.GetWorkflowYaml()), JobKey: spec.GetJobKey(), | |
| 204 | 208 | ContextsJSON: spec.GetContexts(), MatrixJSON: spec.GetMatrix(), Repo: spec.GetRepo(), | |
| 205 | 209 | TimeoutMinutes: int(spec.GetTimeoutMinutes()), Deadline: time.Duration(spec.GetDeadlineMs()) * time.Millisecond, | |
| 206 | 210 | Runner: executor.RunnerInfo{Name: spec.GetRunnerName(), Environment: env}, | |
| 207 | 211 | RunnerVersion: spec.GetRunnerVersion(), | |
| 208 | 212 | Home: su.Home, ToolCache: os.Getenv("RUNNER_TOOL_CACHE"), BaseEnv: stepEnv(os.Environ(), su), Credential: su.Credential, | |
| 209 | - | Debug: spec.GetDebug(), Reporter: rep, Log: zerowrap.FromCtx(ctx), | |
| 213 | + | Debug: spec.GetDebug(), Reporter: rep, Actions: executor.ActionsConfig{Resolver: res.resolve}, Log: zerowrap.FromCtx(ctx), | |
| 210 | 214 | TermAfter: cfg.TermAfter, KillAfter: cfg.KillAfter, WaitDelay: cfg.WaitDelay, | |
| 211 | 215 | }) | |
| 212 | - | if res.Conclusion == executor.ConclusionSuccess { | |
| 216 | + | if result.Conclusion == executor.ConclusionSuccess { | |
| 213 | 217 | return 0 | |
| 214 | 218 | } | |
| 215 | 219 | return 1 | |
| @@ -166,6 +166,7 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||||
| 166 | } | 166 | } | ||
| 167 | canceller := executor.NewCanceller() | 167 | canceller := executor.NewCanceller() | ||
| 168 | rep := &frameReporter{conn: conn, failed: make(chan struct{})} | 168 | rep := &frameReporter{conn: conn, failed: make(chan struct{})} | ||
| 169 | + | res := newResolver(conn) | |||
| 169 | 170 | ||||
| 170 | // Frames from the host while the job runs: a Cancel stops the job; a lost | 171 | // Frames from the host while the job runs: a Cancel stops the job; a lost | ||
| 171 | // host does too, like a runner shutdown. The reader ends with the stream: the | 172 | // host does too, like a runner shutdown. The reader ends with the stream: the | ||
| @@ -183,6 +184,9 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||||
| 183 | if c := f.GetCancel(); c != nil { | 184 | if c := f.GetCancel(); c != nil { | ||
| 184 | canceller.Cancel(cancelKind(c.GetKind())) | 185 | canceller.Cancel(cancelKind(c.GetKind())) | ||
| 185 | } | 186 | } | ||
| 187 | + | if r := f.GetResolveResponse(); r != nil { | |||
| 188 | + | res.deliver(r) | |||
| 189 | + | } | |||
| 186 | } | 190 | } | ||
| 187 | }() | 191 | }() | ||
| 188 | // The executor's own context (SIGTERM forwarded by init: the sandbox is | 192 | // The executor's own context (SIGTERM forwarded by init: the sandbox is | ||
| @@ -199,17 +203,17 @@ func runJob(ctx context.Context, conn *execstream.Conn, cfg ExecConfig, spec *ex | |||||
| 199 | }() | 203 | }() | ||
| 200 | 204 | ||||
| 201 | env := spec.GetRunnerEnvironment() | 205 | env := spec.GetRunnerEnvironment() | ||
| 202 | - | res := executor.Run(canceller, executor.Config{ | 206 | + | result := executor.Run(canceller, executor.Config{ |
| 203 | WorkflowPath: spec.GetWorkflowPath(), WorkflowYAML: []byte(spec.GetWorkflowYaml()), JobKey: spec.GetJobKey(), | 207 | WorkflowPath: spec.GetWorkflowPath(), WorkflowYAML: []byte(spec.GetWorkflowYaml()), JobKey: spec.GetJobKey(), | ||
| 204 | ContextsJSON: spec.GetContexts(), MatrixJSON: spec.GetMatrix(), Repo: spec.GetRepo(), | 208 | ContextsJSON: spec.GetContexts(), MatrixJSON: spec.GetMatrix(), Repo: spec.GetRepo(), | ||
| 205 | TimeoutMinutes: int(spec.GetTimeoutMinutes()), Deadline: time.Duration(spec.GetDeadlineMs()) * time.Millisecond, | 209 | TimeoutMinutes: int(spec.GetTimeoutMinutes()), Deadline: time.Duration(spec.GetDeadlineMs()) * time.Millisecond, | ||
| 206 | Runner: executor.RunnerInfo{Name: spec.GetRunnerName(), Environment: env}, | 210 | Runner: executor.RunnerInfo{Name: spec.GetRunnerName(), Environment: env}, | ||
| 207 | RunnerVersion: spec.GetRunnerVersion(), | 211 | RunnerVersion: spec.GetRunnerVersion(), | ||
| 208 | Home: su.Home, ToolCache: os.Getenv("RUNNER_TOOL_CACHE"), BaseEnv: stepEnv(os.Environ(), su), Credential: su.Credential, | 212 | Home: su.Home, ToolCache: os.Getenv("RUNNER_TOOL_CACHE"), BaseEnv: stepEnv(os.Environ(), su), Credential: su.Credential, | ||
| 209 | - | Debug: spec.GetDebug(), Reporter: rep, Log: zerowrap.FromCtx(ctx), | 213 | + | Debug: spec.GetDebug(), Reporter: rep, Actions: executor.ActionsConfig{Resolver: res.resolve}, Log: zerowrap.FromCtx(ctx), |
| 210 | TermAfter: cfg.TermAfter, KillAfter: cfg.KillAfter, WaitDelay: cfg.WaitDelay, | 214 | TermAfter: cfg.TermAfter, KillAfter: cfg.KillAfter, WaitDelay: cfg.WaitDelay, | ||
| 211 | }) | 215 | }) | ||
| 212 | - | if res.Conclusion == executor.ConclusionSuccess { | 216 | + | if result.Conclusion == executor.ConclusionSuccess { |
| 213 | return 0 | 217 | return 0 | ||
| 214 | } | 218 | } | ||
| 215 | return 1 | 219 | return 1 | ||
A internal/guest/resolver.go +69 -0
| @@ -0,0 +1,69 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package guest | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "errors" | |
| 8 | + | "sync" | |
| 9 | + | ||
| 10 | + | "bnema.dev/ermite-runner/internal/execstream" | |
| 11 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |
| 12 | + | "bnema.dev/ermite-runner/internal/executor" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // resolver sends ResolveRequest frames to the host and matches the | |
| 16 | + | // ResolveResponse frames that come back (the job token stays in the host). | |
| 17 | + | type resolver struct { | |
| 18 | + | conn *execstream.Conn | |
| 19 | + | mu sync.Mutex | |
| 20 | + | next int64 | |
| 21 | + | wait map[int64]chan *execpb.ResolveResponse | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | func newResolver(conn *execstream.Conn) *resolver { | |
| 25 | + | return &resolver{conn: conn, wait: map[int64]chan *execpb.ResolveResponse{}} | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | func (r *resolver) deliver(resp *execpb.ResolveResponse) { | |
| 29 | + | r.mu.Lock() | |
| 30 | + | ch := r.wait[resp.Id] | |
| 31 | + | delete(r.wait, resp.Id) | |
| 32 | + | r.mu.Unlock() | |
| 33 | + | if ch != nil { | |
| 34 | + | ch <- resp | |
| 35 | + | } | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | // resolve is executor.Config.Resolver. | |
| 39 | + | func (r *resolver) resolve(ctx context.Context, uses []string) ([]executor.ResolvedAction, error) { | |
| 40 | + | ch := make(chan *execpb.ResolveResponse, 1) | |
| 41 | + | r.mu.Lock() | |
| 42 | + | r.next++ | |
| 43 | + | id := r.next | |
| 44 | + | r.wait[id] = ch | |
| 45 | + | r.mu.Unlock() | |
| 46 | + | forget := func() { r.mu.Lock(); delete(r.wait, id); r.mu.Unlock() } | |
| 47 | + | req := &execpb.ResolveRequest{Id: id, Uses: uses} | |
| 48 | + | if err := r.conn.Send(&execpb.Frame{Body: &execpb.Frame_ResolveRequest{ResolveRequest: req}}); err != nil { | |
| 49 | + | forget() | |
| 50 | + | return nil, err | |
| 51 | + | } | |
| 52 | + | select { | |
| 53 | + | case resp := <-ch: | |
| 54 | + | if resp.Error != "" { | |
| 55 | + | return nil, errors.New(resp.Error) | |
| 56 | + | } | |
| 57 | + | out := make([]executor.ResolvedAction, len(resp.Actions)) | |
| 58 | + | for i, a := range resp.Actions { | |
| 59 | + | out[i] = executor.ResolvedAction{ | |
| 60 | + | Ref: a.Ref, NameWithOwner: a.NameWithOwner, Path: a.Path, ResolvedSHA: a.ResolvedSha, | |
| 61 | + | TarballURL: a.TarballUrl, Token: a.Token, Error: a.Error, | |
| 62 | + | } | |
| 63 | + | } | |
| 64 | + | return out, nil | |
| 65 | + | case <-ctx.Done(): | |
| 66 | + | forget() | |
| 67 | + | return nil, ctx.Err() | |
| 68 | + | } | |
| 69 | + | } | |
| @@ -0,0 +1,69 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | ||||
| 3 | + | package guest | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "errors" | |||
| 8 | + | "sync" | |||
| 9 | + | ||||
| 10 | + | "bnema.dev/ermite-runner/internal/execstream" | |||
| 11 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |||
| 12 | + | "bnema.dev/ermite-runner/internal/executor" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // resolver sends ResolveRequest frames to the host and matches the | |||
| 16 | + | // ResolveResponse frames that come back (the job token stays in the host). | |||
| 17 | + | type resolver struct { | |||
| 18 | + | conn *execstream.Conn | |||
| 19 | + | mu sync.Mutex | |||
| 20 | + | next int64 | |||
| 21 | + | wait map[int64]chan *execpb.ResolveResponse | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | func newResolver(conn *execstream.Conn) *resolver { | |||
| 25 | + | return &resolver{conn: conn, wait: map[int64]chan *execpb.ResolveResponse{}} | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | func (r *resolver) deliver(resp *execpb.ResolveResponse) { | |||
| 29 | + | r.mu.Lock() | |||
| 30 | + | ch := r.wait[resp.Id] | |||
| 31 | + | delete(r.wait, resp.Id) | |||
| 32 | + | r.mu.Unlock() | |||
| 33 | + | if ch != nil { | |||
| 34 | + | ch <- resp | |||
| 35 | + | } | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | // resolve is executor.Config.Resolver. | |||
| 39 | + | func (r *resolver) resolve(ctx context.Context, uses []string) ([]executor.ResolvedAction, error) { | |||
| 40 | + | ch := make(chan *execpb.ResolveResponse, 1) | |||
| 41 | + | r.mu.Lock() | |||
| 42 | + | r.next++ | |||
| 43 | + | id := r.next | |||
| 44 | + | r.wait[id] = ch | |||
| 45 | + | r.mu.Unlock() | |||
| 46 | + | forget := func() { r.mu.Lock(); delete(r.wait, id); r.mu.Unlock() } | |||
| 47 | + | req := &execpb.ResolveRequest{Id: id, Uses: uses} | |||
| 48 | + | if err := r.conn.Send(&execpb.Frame{Body: &execpb.Frame_ResolveRequest{ResolveRequest: req}}); err != nil { | |||
| 49 | + | forget() | |||
| 50 | + | return nil, err | |||
| 51 | + | } | |||
| 52 | + | select { | |||
| 53 | + | case resp := <-ch: | |||
| 54 | + | if resp.Error != "" { | |||
| 55 | + | return nil, errors.New(resp.Error) | |||
| 56 | + | } | |||
| 57 | + | out := make([]executor.ResolvedAction, len(resp.Actions)) | |||
| 58 | + | for i, a := range resp.Actions { | |||
| 59 | + | out[i] = executor.ResolvedAction{ | |||
| 60 | + | Ref: a.Ref, NameWithOwner: a.NameWithOwner, Path: a.Path, ResolvedSHA: a.ResolvedSha, | |||
| 61 | + | TarballURL: a.TarballUrl, Token: a.Token, Error: a.Error, | |||
| 62 | + | } | |||
| 63 | + | } | |||
| 64 | + | return out, nil | |||
| 65 | + | case <-ctx.Done(): | |||
| 66 | + | forget() | |||
| 67 | + | return nil, ctx.Err() | |||
| 68 | + | } | |||
| 69 | + | } | |||
M internal/host/job.go +6 -0
| @@ -31,6 +31,7 @@ type jobClient interface { | |||
| 31 | 31 | Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) | |
| 32 | 32 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | |
| 33 | 33 | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | |
| 34 | + | ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) | |
| 34 | 35 | } | |
| 35 | 36 | ||
| 36 | 37 | var _ jobClient = ermiteconnect.RunnerServiceClient(nil) | |
| @@ -570,6 +571,10 @@ func (j *jobRun) execute(ctx context.Context, rt *Runner, stopHard <-chan struct | |||
| 570 | 571 | j.failEarly(fmt.Sprintf("The job's contexts cannot be prepared: %v", err)) | |
| 571 | 572 | return failure() | |
| 572 | 573 | } | |
| 574 | + | if j.contexts, err = SetGitHubToken(j.contexts, j.job.GithubToken); err != nil { | |
| 575 | + | j.failEarly(fmt.Sprintf("The job's contexts cannot be prepared: %v", err)) | |
| 576 | + | return failure() | |
| 577 | + | } | |
| 573 | 578 | sb, err := rt.Driver.New() | |
| 574 | 579 | if err != nil { | |
| 575 | 580 | j.failEarly(fmt.Sprintf("The runner has no free sandbox: %v", err)) | |
| @@ -648,6 +653,7 @@ func (j *jobRun) drive(conn sandbox.Conn, rt *Runner) (*execpb.Exit, error) { | |||
| 648 | 653 | StepStarted: j.stepStarted, StepCompleted: j.stepCompleted, | |
| 649 | 654 | Log: j.logLine, | |
| 650 | 655 | JobResult: func(r *execpb.JobResult) { j.mu.Lock(); j.result = r; j.mu.Unlock() }, | |
| 656 | + | Resolve: j.resolveActions, | |
| 651 | 657 | }) | |
| 652 | 658 | return exit, err | |
| 653 | 659 | } | |
| @@ -31,6 +31,7 @@ type jobClient interface { | |||||
| 31 | Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) | 31 | Heartbeat(context.Context, *connect.Request[ermite.HeartbeatRequest]) (*connect.Response[ermite.HeartbeatResponse], error) | ||
| 32 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | 32 | WatchJob(context.Context, *connect.Request[ermite.WatchJobRequest]) (*connect.ServerStreamForClient[ermite.WatchJobResponse], error) | ||
| 33 | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | 33 | CompleteJob(context.Context, *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) | ||
| 34 | + | ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) | |||
| 34 | } | 35 | } | ||
| 35 | 36 | ||||
| 36 | var _ jobClient = ermiteconnect.RunnerServiceClient(nil) | 37 | var _ jobClient = ermiteconnect.RunnerServiceClient(nil) | ||
| @@ -570,6 +571,10 @@ func (j *jobRun) execute(ctx context.Context, rt *Runner, stopHard <-chan struct | |||||
| 570 | j.failEarly(fmt.Sprintf("The job's contexts cannot be prepared: %v", err)) | 571 | j.failEarly(fmt.Sprintf("The job's contexts cannot be prepared: %v", err)) | ||
| 571 | return failure() | 572 | return failure() | ||
| 572 | } | 573 | } | ||
| 574 | + | if j.contexts, err = SetGitHubToken(j.contexts, j.job.GithubToken); err != nil { | |||
| 575 | + | j.failEarly(fmt.Sprintf("The job's contexts cannot be prepared: %v", err)) | |||
| 576 | + | return failure() | |||
| 577 | + | } | |||
| 573 | sb, err := rt.Driver.New() | 578 | sb, err := rt.Driver.New() | ||
| 574 | if err != nil { | 579 | if err != nil { | ||
| 575 | j.failEarly(fmt.Sprintf("The runner has no free sandbox: %v", err)) | 580 | j.failEarly(fmt.Sprintf("The runner has no free sandbox: %v", err)) | ||
| @@ -648,6 +653,7 @@ func (j *jobRun) drive(conn sandbox.Conn, rt *Runner) (*execpb.Exit, error) { | |||||
| 648 | StepStarted: j.stepStarted, StepCompleted: j.stepCompleted, | 653 | StepStarted: j.stepStarted, StepCompleted: j.stepCompleted, | ||
| 649 | Log: j.logLine, | 654 | Log: j.logLine, | ||
| 650 | JobResult: func(r *execpb.JobResult) { j.mu.Lock(); j.result = r; j.mu.Unlock() }, | 655 | JobResult: func(r *execpb.JobResult) { j.mu.Lock(); j.result = r; j.mu.Unlock() }, | ||
| 656 | + | Resolve: j.resolveActions, | |||
| 651 | }) | 657 | }) | ||
| 652 | return exit, err | 658 | return exit, err | ||
| 653 | } | 659 | } | ||
A internal/host/resolve.go +121 -0
| @@ -0,0 +1,121 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package host | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "encoding/json" | |
| 8 | + | "fmt" | |
| 9 | + | ||
| 10 | + | "connectrpc.com/connect" | |
| 11 | + | ||
| 12 | + | ermite "bnema.dev/ermite-api/gen/ermite" | |
| 13 | + | ||
| 14 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |
| 15 | + | ) | |
| 16 | + | ||
| 17 | + | // maxResolveRefs is the limit of ResolveActionsRequest.refs. | |
| 18 | + | const maxResolveRefs = 100 | |
| 19 | + | ||
| 20 | + | // resolveActions answers a ResolveRequest of the executor with | |
| 21 | + | // RunnerService.ResolveActions. The job token stays here: the executor only | |
| 22 | + | // gets what the server returns (the per-archive tokens included, which it | |
| 23 | + | // masks). A call that fails without a verdict is retried like the other job | |
| 24 | + | // calls; a failure of the call itself is reported to the executor, which fails | |
| 25 | + | // "Set up job" (ActionManager: FailedToResolveActionDownloadInfoException). | |
| 26 | + | func (j *jobRun) resolveActions(req *execpb.ResolveRequest) *execpb.ResolveResponse { | |
| 27 | + | if len(req.Uses) > maxResolveRefs { | |
| 28 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("cannot resolve %d actions at once (the limit is %d)", len(req.Uses), maxResolveRefs)} | |
| 29 | + | } | |
| 30 | + | refs := make([]*ermite.ActionRef, len(req.Uses)) | |
| 31 | + | for i, u := range req.Uses { | |
| 32 | + | refs[i] = &ermite.ActionRef{Uses: u} | |
| 33 | + | } | |
| 34 | + | b := backoff{min: j.t.BackoffMin, max: j.t.BackoffMax} | |
| 35 | + | for attempt := 1; ; attempt++ { | |
| 36 | + | cctx, cancel := context.WithTimeout(j.ctx, j.t.CallTimeout) | |
| 37 | + | resp, err := j.client.ResolveActions(cctx, connect.NewRequest(&ermite.ResolveActionsRequest{JobId: j.job.JobId, Refs: refs})) | |
| 38 | + | cancel() | |
| 39 | + | if err == nil { | |
| 40 | + | return resolveResponse(resp.Msg, len(req.Uses)) | |
| 41 | + | } | |
| 42 | + | if j.ctx.Err() != nil { | |
| 43 | + | return &execpb.ResolveResponse{Error: "the job was cancelled while its actions were resolved"} | |
| 44 | + | } | |
| 45 | + | switch classify(err) { | |
| 46 | + | case kindEnded, kindAbandoned: | |
| 47 | + | j.abandon(err) | |
| 48 | + | return &execpb.ResolveResponse{Error: "the server ended the job"} | |
| 49 | + | case kindPermanent: | |
| 50 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("resolving actions failed: %s", connect.CodeOf(err))} | |
| 51 | + | } | |
| 52 | + | j.log.Warn().Err(err).Int("attempt", attempt).Msg("ResolveActions failed") | |
| 53 | + | if attempt >= 3 || !sleep(j.ctx, b.next()) { | |
| 54 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("resolving actions failed: %s", connect.CodeOf(err))} | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | func resolveResponse(m *ermite.ResolveActionsResponse, want int) *execpb.ResolveResponse { | |
| 60 | + | if len(m.Actions) != want { | |
| 61 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("the server resolved %d actions out of %d", len(m.Actions), want)} | |
| 62 | + | } | |
| 63 | + | out := &execpb.ResolveResponse{} | |
| 64 | + | for _, a := range m.Actions { | |
| 65 | + | out.Actions = append(out.Actions, &execpb.ResolvedAction{ | |
| 66 | + | Ref: a.Ref, NameWithOwner: a.NameWithOwner, Path: a.Path, ResolvedSha: a.ResolvedSha, | |
| 67 | + | TarballUrl: a.TarballUrl, Token: a.Token, Source: a.Source.String(), Error: a.Error, | |
| 68 | + | }) | |
| 69 | + | } | |
| 70 | + | return out | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // SetGitHubToken returns the contexts JSON of a job with github.token set to | |
| 74 | + | // token (the job repository token, Job.github_token). The key is added first, | |
| 75 | + | // like the runner does (ExecutionContext.cs:1023: token, then what the server | |
| 76 | + | // gave); an empty token leaves the contexts as they are. | |
| 77 | + | func SetGitHubToken(contexts, token string) (string, error) { | |
| 78 | + | if token == "" { | |
| 79 | + | return contexts, nil | |
| 80 | + | } | |
| 81 | + | top, err := parseObject([]byte(contexts)) | |
| 82 | + | if err != nil { | |
| 83 | + | return "", fmt.Errorf("job contexts: %w", err) | |
| 84 | + | } | |
| 85 | + | gi := -1 | |
| 86 | + | for i, m := range top { | |
| 87 | + | if m.key == "github" { | |
| 88 | + | gi = i | |
| 89 | + | } | |
| 90 | + | } | |
| 91 | + | var gh []jsonMember | |
| 92 | + | if gi >= 0 { | |
| 93 | + | if gh, err = parseObject(top[gi].val); err != nil { | |
| 94 | + | return "", fmt.Errorf("job contexts: github: %w", err) | |
| 95 | + | } | |
| 96 | + | } | |
| 97 | + | tv, err := json.Marshal(token) | |
| 98 | + | if err != nil { | |
| 99 | + | return "", err | |
| 100 | + | } | |
| 101 | + | set := false | |
| 102 | + | for i, m := range gh { | |
| 103 | + | if m.key == "token" { | |
| 104 | + | gh[i].val, set = tv, true | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | if !set { | |
| 108 | + | gh = append([]jsonMember{{"token", tv}}, gh...) | |
| 109 | + | } | |
| 110 | + | ghv, err := encodeObject(gh) | |
| 111 | + | if err != nil { | |
| 112 | + | return "", err | |
| 113 | + | } | |
| 114 | + | if gi >= 0 { | |
| 115 | + | top[gi].val = ghv | |
| 116 | + | } else { | |
| 117 | + | top = append(top, jsonMember{"github", ghv}) | |
| 118 | + | } | |
| 119 | + | out, err := encodeObject(top) | |
| 120 | + | return string(out), err | |
| 121 | + | } | |
| @@ -0,0 +1,121 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | ||||
| 3 | + | package host | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "encoding/json" | |||
| 8 | + | "fmt" | |||
| 9 | + | ||||
| 10 | + | "connectrpc.com/connect" | |||
| 11 | + | ||||
| 12 | + | ermite "bnema.dev/ermite-api/gen/ermite" | |||
| 13 | + | ||||
| 14 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |||
| 15 | + | ) | |||
| 16 | + | ||||
| 17 | + | // maxResolveRefs is the limit of ResolveActionsRequest.refs. | |||
| 18 | + | const maxResolveRefs = 100 | |||
| 19 | + | ||||
| 20 | + | // resolveActions answers a ResolveRequest of the executor with | |||
| 21 | + | // RunnerService.ResolveActions. The job token stays here: the executor only | |||
| 22 | + | // gets what the server returns (the per-archive tokens included, which it | |||
| 23 | + | // masks). A call that fails without a verdict is retried like the other job | |||
| 24 | + | // calls; a failure of the call itself is reported to the executor, which fails | |||
| 25 | + | // "Set up job" (ActionManager: FailedToResolveActionDownloadInfoException). | |||
| 26 | + | func (j *jobRun) resolveActions(req *execpb.ResolveRequest) *execpb.ResolveResponse { | |||
| 27 | + | if len(req.Uses) > maxResolveRefs { | |||
| 28 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("cannot resolve %d actions at once (the limit is %d)", len(req.Uses), maxResolveRefs)} | |||
| 29 | + | } | |||
| 30 | + | refs := make([]*ermite.ActionRef, len(req.Uses)) | |||
| 31 | + | for i, u := range req.Uses { | |||
| 32 | + | refs[i] = &ermite.ActionRef{Uses: u} | |||
| 33 | + | } | |||
| 34 | + | b := backoff{min: j.t.BackoffMin, max: j.t.BackoffMax} | |||
| 35 | + | for attempt := 1; ; attempt++ { | |||
| 36 | + | cctx, cancel := context.WithTimeout(j.ctx, j.t.CallTimeout) | |||
| 37 | + | resp, err := j.client.ResolveActions(cctx, connect.NewRequest(&ermite.ResolveActionsRequest{JobId: j.job.JobId, Refs: refs})) | |||
| 38 | + | cancel() | |||
| 39 | + | if err == nil { | |||
| 40 | + | return resolveResponse(resp.Msg, len(req.Uses)) | |||
| 41 | + | } | |||
| 42 | + | if j.ctx.Err() != nil { | |||
| 43 | + | return &execpb.ResolveResponse{Error: "the job was cancelled while its actions were resolved"} | |||
| 44 | + | } | |||
| 45 | + | switch classify(err) { | |||
| 46 | + | case kindEnded, kindAbandoned: | |||
| 47 | + | j.abandon(err) | |||
| 48 | + | return &execpb.ResolveResponse{Error: "the server ended the job"} | |||
| 49 | + | case kindPermanent: | |||
| 50 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("resolving actions failed: %s", connect.CodeOf(err))} | |||
| 51 | + | } | |||
| 52 | + | j.log.Warn().Err(err).Int("attempt", attempt).Msg("ResolveActions failed") | |||
| 53 | + | if attempt >= 3 || !sleep(j.ctx, b.next()) { | |||
| 54 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("resolving actions failed: %s", connect.CodeOf(err))} | |||
| 55 | + | } | |||
| 56 | + | } | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | func resolveResponse(m *ermite.ResolveActionsResponse, want int) *execpb.ResolveResponse { | |||
| 60 | + | if len(m.Actions) != want { | |||
| 61 | + | return &execpb.ResolveResponse{Error: fmt.Sprintf("the server resolved %d actions out of %d", len(m.Actions), want)} | |||
| 62 | + | } | |||
| 63 | + | out := &execpb.ResolveResponse{} | |||
| 64 | + | for _, a := range m.Actions { | |||
| 65 | + | out.Actions = append(out.Actions, &execpb.ResolvedAction{ | |||
| 66 | + | Ref: a.Ref, NameWithOwner: a.NameWithOwner, Path: a.Path, ResolvedSha: a.ResolvedSha, | |||
| 67 | + | TarballUrl: a.TarballUrl, Token: a.Token, Source: a.Source.String(), Error: a.Error, | |||
| 68 | + | }) | |||
| 69 | + | } | |||
| 70 | + | return out | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // SetGitHubToken returns the contexts JSON of a job with github.token set to | |||
| 74 | + | // token (the job repository token, Job.github_token). The key is added first, | |||
| 75 | + | // like the runner does (ExecutionContext.cs:1023: token, then what the server | |||
| 76 | + | // gave); an empty token leaves the contexts as they are. | |||
| 77 | + | func SetGitHubToken(contexts, token string) (string, error) { | |||
| 78 | + | if token == "" { | |||
| 79 | + | return contexts, nil | |||
| 80 | + | } | |||
| 81 | + | top, err := parseObject([]byte(contexts)) | |||
| 82 | + | if err != nil { | |||
| 83 | + | return "", fmt.Errorf("job contexts: %w", err) | |||
| 84 | + | } | |||
| 85 | + | gi := -1 | |||
| 86 | + | for i, m := range top { | |||
| 87 | + | if m.key == "github" { | |||
| 88 | + | gi = i | |||
| 89 | + | } | |||
| 90 | + | } | |||
| 91 | + | var gh []jsonMember | |||
| 92 | + | if gi >= 0 { | |||
| 93 | + | if gh, err = parseObject(top[gi].val); err != nil { | |||
| 94 | + | return "", fmt.Errorf("job contexts: github: %w", err) | |||
| 95 | + | } | |||
| 96 | + | } | |||
| 97 | + | tv, err := json.Marshal(token) | |||
| 98 | + | if err != nil { | |||
| 99 | + | return "", err | |||
| 100 | + | } | |||
| 101 | + | set := false | |||
| 102 | + | for i, m := range gh { | |||
| 103 | + | if m.key == "token" { | |||
| 104 | + | gh[i].val, set = tv, true | |||
| 105 | + | } | |||
| 106 | + | } | |||
| 107 | + | if !set { | |||
| 108 | + | gh = append([]jsonMember{{"token", tv}}, gh...) | |||
| 109 | + | } | |||
| 110 | + | ghv, err := encodeObject(gh) | |||
| 111 | + | if err != nil { | |||
| 112 | + | return "", err | |||
| 113 | + | } | |||
| 114 | + | if gi >= 0 { | |||
| 115 | + | top[gi].val = ghv | |||
| 116 | + | } else { | |||
| 117 | + | top = append(top, jsonMember{"github", ghv}) | |||
| 118 | + | } | |||
| 119 | + | out, err := encodeObject(top) | |||
| 120 | + | return string(out), err | |||
| 121 | + | } | |||
A internal/host/resolve_test.go +97 -0
| @@ -0,0 +1,97 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package host | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "context" | |
| 7 | + | "encoding/json" | |
| 8 | + | "strings" | |
| 9 | + | "testing" | |
| 10 | + | "time" | |
| 11 | + | ||
| 12 | + | "connectrpc.com/connect" | |
| 13 | + | "github.com/bnema/zerowrap" | |
| 14 | + | ||
| 15 | + | ermite "bnema.dev/ermite-api/gen/ermite" | |
| 16 | + | ||
| 17 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |
| 18 | + | ) | |
| 19 | + | ||
| 20 | + | type resolveStub struct { | |
| 21 | + | stubClient | |
| 22 | + | got *ermite.ResolveActionsRequest | |
| 23 | + | resp *ermite.ResolveActionsResponse | |
| 24 | + | err error | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | func (c *resolveStub) ResolveActions(_ context.Context, r *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) { | |
| 28 | + | c.got = r.Msg | |
| 29 | + | if c.err != nil { | |
| 30 | + | return nil, c.err | |
| 31 | + | } | |
| 32 | + | return connect.NewResponse(c.resp), nil | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | func resolveJob(c jobClient) *jobRun { | |
| 36 | + | return &jobRun{client: c, ctx: context.Background(), job: &ermite.Job{JobId: "j1"}, log: zerowrap.Logger{}, | |
| 37 | + | t: Timing{CallTimeout: time.Second, BackoffMin: time.Millisecond, BackoffMax: time.Millisecond}} | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | func TestResolveActions(t *testing.T) { | |
| 41 | + | c := &resolveStub{resp: &ermite.ResolveActionsResponse{Actions: []*ermite.ResolvedAction{ | |
| 42 | + | {Ref: "a/b@v1", NameWithOwner: "a/b", ResolvedSha: strings.Repeat("a", 40), TarballUrl: "https://x/t", Token: "tok", Source: ermite.ActionSource_ACTION_SOURCE_ERMITE}, | |
| 43 | + | {Ref: "a/c@v1", Error: "boom"}, | |
| 44 | + | }}} | |
| 45 | + | resp := resolveJob(c).resolveActions(&execpb.ResolveRequest{Id: 7, Uses: []string{"a/b@v1", "a/c@v1"}}) | |
| 46 | + | if resp.Error != "" || len(resp.Actions) != 2 { | |
| 47 | + | t.Fatalf("%+v", resp) | |
| 48 | + | } | |
| 49 | + | if c.got.JobId != "j1" || len(c.got.Refs) != 2 || c.got.Refs[1].Uses != "a/c@v1" { | |
| 50 | + | t.Errorf("request %+v", c.got) | |
| 51 | + | } | |
| 52 | + | if a := resp.Actions[0]; a.Token != "tok" || a.TarballUrl != "https://x/t" || a.NameWithOwner != "a/b" { | |
| 53 | + | t.Errorf("%+v", a) | |
| 54 | + | } | |
| 55 | + | if resp.Actions[1].Error != "boom" { | |
| 56 | + | t.Errorf("%+v", resp.Actions[1]) | |
| 57 | + | } | |
| 58 | + | } | |
| 59 | + | ||
| 60 | + | func TestResolveActionsFailures(t *testing.T) { | |
| 61 | + | c := &resolveStub{err: connect.NewError(connect.CodeInvalidArgument, nil)} | |
| 62 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: []string{"a/b@v1"}}); !strings.Contains(r.Error, "invalid_argument") { | |
| 63 | + | t.Errorf("%+v", r) | |
| 64 | + | } | |
| 65 | + | c = &resolveStub{resp: &ermite.ResolveActionsResponse{}} | |
| 66 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: []string{"a/b@v1"}}); r.Error == "" { | |
| 67 | + | t.Errorf("a short answer must fail: %+v", r) | |
| 68 | + | } | |
| 69 | + | many := make([]string, maxResolveRefs+1) | |
| 70 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: many}); r.Error == "" { | |
| 71 | + | t.Error("more than 100 refs must fail") | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | func TestSetGitHubToken(t *testing.T) { | |
| 76 | + | cases := map[string]string{ | |
| 77 | + | `{"github":{"job":"j","token":""},"needs":{}}`: `{"github":{"job":"j","token":"T"},"needs":{}}`, | |
| 78 | + | `{"github":{"job":"j"}}`: `{"github":{"token":"T","job":"j"}}`, | |
| 79 | + | `{"needs":{}}`: `{"needs":{},"github":{"token":"T"}}`, | |
| 80 | + | } | |
| 81 | + | for in, want := range cases { | |
| 82 | + | got, err := SetGitHubToken(in, "T") | |
| 83 | + | if err != nil || got != want { | |
| 84 | + | t.Errorf("%s: %s %v, want %s", in, got, err, want) | |
| 85 | + | } | |
| 86 | + | } | |
| 87 | + | if got, _ := SetGitHubToken(`{"a":1}`, ""); got != `{"a":1}` { | |
| 88 | + | t.Errorf("empty token changed the contexts: %s", got) | |
| 89 | + | } | |
| 90 | + | if _, err := SetGitHubToken(`[]`, "T"); err == nil { | |
| 91 | + | t.Error("not an object") | |
| 92 | + | } | |
| 93 | + | var m map[string]any | |
| 94 | + | if got, _ := SetGitHubToken(`{}`, `a"b\`); json.Unmarshal([]byte(got), &m) != nil { | |
| 95 | + | t.Errorf("invalid JSON: %s", got) | |
| 96 | + | } | |
| 97 | + | } | |
| @@ -0,0 +1,97 @@ | |||||
| 1 | + | // SPDX-License-Identifier: MIT | |||
| 2 | + | ||||
| 3 | + | package host | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "context" | |||
| 7 | + | "encoding/json" | |||
| 8 | + | "strings" | |||
| 9 | + | "testing" | |||
| 10 | + | "time" | |||
| 11 | + | ||||
| 12 | + | "connectrpc.com/connect" | |||
| 13 | + | "github.com/bnema/zerowrap" | |||
| 14 | + | ||||
| 15 | + | ermite "bnema.dev/ermite-api/gen/ermite" | |||
| 16 | + | ||||
| 17 | + | "bnema.dev/ermite-runner/internal/execstream/execpb" | |||
| 18 | + | ) | |||
| 19 | + | ||||
| 20 | + | type resolveStub struct { | |||
| 21 | + | stubClient | |||
| 22 | + | got *ermite.ResolveActionsRequest | |||
| 23 | + | resp *ermite.ResolveActionsResponse | |||
| 24 | + | err error | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | func (c *resolveStub) ResolveActions(_ context.Context, r *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) { | |||
| 28 | + | c.got = r.Msg | |||
| 29 | + | if c.err != nil { | |||
| 30 | + | return nil, c.err | |||
| 31 | + | } | |||
| 32 | + | return connect.NewResponse(c.resp), nil | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | func resolveJob(c jobClient) *jobRun { | |||
| 36 | + | return &jobRun{client: c, ctx: context.Background(), job: &ermite.Job{JobId: "j1"}, log: zerowrap.Logger{}, | |||
| 37 | + | t: Timing{CallTimeout: time.Second, BackoffMin: time.Millisecond, BackoffMax: time.Millisecond}} | |||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | func TestResolveActions(t *testing.T) { | |||
| 41 | + | c := &resolveStub{resp: &ermite.ResolveActionsResponse{Actions: []*ermite.ResolvedAction{ | |||
| 42 | + | {Ref: "a/b@v1", NameWithOwner: "a/b", ResolvedSha: strings.Repeat("a", 40), TarballUrl: "https://x/t", Token: "tok", Source: ermite.ActionSource_ACTION_SOURCE_ERMITE}, | |||
| 43 | + | {Ref: "a/c@v1", Error: "boom"}, | |||
| 44 | + | }}} | |||
| 45 | + | resp := resolveJob(c).resolveActions(&execpb.ResolveRequest{Id: 7, Uses: []string{"a/b@v1", "a/c@v1"}}) | |||
| 46 | + | if resp.Error != "" || len(resp.Actions) != 2 { | |||
| 47 | + | t.Fatalf("%+v", resp) | |||
| 48 | + | } | |||
| 49 | + | if c.got.JobId != "j1" || len(c.got.Refs) != 2 || c.got.Refs[1].Uses != "a/c@v1" { | |||
| 50 | + | t.Errorf("request %+v", c.got) | |||
| 51 | + | } | |||
| 52 | + | if a := resp.Actions[0]; a.Token != "tok" || a.TarballUrl != "https://x/t" || a.NameWithOwner != "a/b" { | |||
| 53 | + | t.Errorf("%+v", a) | |||
| 54 | + | } | |||
| 55 | + | if resp.Actions[1].Error != "boom" { | |||
| 56 | + | t.Errorf("%+v", resp.Actions[1]) | |||
| 57 | + | } | |||
| 58 | + | } | |||
| 59 | + | ||||
| 60 | + | func TestResolveActionsFailures(t *testing.T) { | |||
| 61 | + | c := &resolveStub{err: connect.NewError(connect.CodeInvalidArgument, nil)} | |||
| 62 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: []string{"a/b@v1"}}); !strings.Contains(r.Error, "invalid_argument") { | |||
| 63 | + | t.Errorf("%+v", r) | |||
| 64 | + | } | |||
| 65 | + | c = &resolveStub{resp: &ermite.ResolveActionsResponse{}} | |||
| 66 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: []string{"a/b@v1"}}); r.Error == "" { | |||
| 67 | + | t.Errorf("a short answer must fail: %+v", r) | |||
| 68 | + | } | |||
| 69 | + | many := make([]string, maxResolveRefs+1) | |||
| 70 | + | if r := resolveJob(c).resolveActions(&execpb.ResolveRequest{Uses: many}); r.Error == "" { | |||
| 71 | + | t.Error("more than 100 refs must fail") | |||
| 72 | + | } | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | func TestSetGitHubToken(t *testing.T) { | |||
| 76 | + | cases := map[string]string{ | |||
| 77 | + | `{"github":{"job":"j","token":""},"needs":{}}`: `{"github":{"job":"j","token":"T"},"needs":{}}`, | |||
| 78 | + | `{"github":{"job":"j"}}`: `{"github":{"token":"T","job":"j"}}`, | |||
| 79 | + | `{"needs":{}}`: `{"needs":{},"github":{"token":"T"}}`, | |||
| 80 | + | } | |||
| 81 | + | for in, want := range cases { | |||
| 82 | + | got, err := SetGitHubToken(in, "T") | |||
| 83 | + | if err != nil || got != want { | |||
| 84 | + | t.Errorf("%s: %s %v, want %s", in, got, err, want) | |||
| 85 | + | } | |||
| 86 | + | } | |||
| 87 | + | if got, _ := SetGitHubToken(`{"a":1}`, ""); got != `{"a":1}` { | |||
| 88 | + | t.Errorf("empty token changed the contexts: %s", got) | |||
| 89 | + | } | |||
| 90 | + | if _, err := SetGitHubToken(`[]`, "T"); err == nil { | |||
| 91 | + | t.Error("not an object") | |||
| 92 | + | } | |||
| 93 | + | var m map[string]any | |||
| 94 | + | if got, _ := SetGitHubToken(`{}`, `a"b\`); json.Unmarshal([]byte(got), &m) != nil { | |||
| 95 | + | t.Errorf("invalid JSON: %s", got) | |||
| 96 | + | } | |||
| 97 | + | } | |||
M internal/host/shutdown_test.go +4 -0
| @@ -46,6 +46,10 @@ func (c *stubClient) WatchJob(context.Context, *connect.Request[ermite.WatchJobR | |||
| 46 | 46 | return nil, connect.NewError(connect.CodeUnimplemented, nil) | |
| 47 | 47 | } | |
| 48 | 48 | ||
| 49 | + | func (c *stubClient) ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) { | |
| 50 | + | return nil, connect.NewError(connect.CodeUnimplemented, nil) | |
| 51 | + | } | |
| 52 | + | ||
| 49 | 53 | func (c *stubClient) CompleteJob(ctx context.Context, _ *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | |
| 50 | 54 | c.mu.Lock() | |
| 51 | 55 | c.deadline, _ = ctx.Deadline() | |
| @@ -46,6 +46,10 @@ func (c *stubClient) WatchJob(context.Context, *connect.Request[ermite.WatchJobR | |||||
| 46 | return nil, connect.NewError(connect.CodeUnimplemented, nil) | 46 | return nil, connect.NewError(connect.CodeUnimplemented, nil) | ||
| 47 | } | 47 | } | ||
| 48 | 48 | ||||
| 49 | + | func (c *stubClient) ResolveActions(context.Context, *connect.Request[ermite.ResolveActionsRequest]) (*connect.Response[ermite.ResolveActionsResponse], error) { | |||
| 50 | + | return nil, connect.NewError(connect.CodeUnimplemented, nil) | |||
| 51 | + | } | |||
| 52 | + | ||||
| 49 | func (c *stubClient) CompleteJob(ctx context.Context, _ *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | 53 | func (c *stubClient) CompleteJob(ctx context.Context, _ *connect.Request[ermite.CompleteJobRequest]) (*connect.Response[ermite.CompleteJobResponse], error) { | ||
| 50 | c.mu.Lock() | 54 | c.mu.Lock() | ||
| 51 | c.deadline, _ = ctx.Deadline() | 55 | c.deadline, _ = ctx.Deadline() | ||