diff --git a/agent-task/m-control-plane-separation-migration/06+05_runner_dispatch_contract/CODE_REVIEW-cloud-G07.md b/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/code_review_cloud_G07_0.log similarity index 54% rename from agent-task/m-control-plane-separation-migration/06+05_runner_dispatch_contract/CODE_REVIEW-cloud-G07.md rename to agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/code_review_cloud_G07_0.log index 97a28c3..90c1284 100644 --- a/agent-task/m-control-plane-separation-migration/06+05_runner_dispatch_contract/CODE_REVIEW-cloud-G07.md +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/code_review_cloud_G07_0.log @@ -25,33 +25,37 @@ task=m-control-plane-separation-migration/06+05_runner_dispatch_contract, plan=0 | 항목 | 완료 여부 | |------|---------| -| [CICD_RUNNER-1] runner job proto 계약 | [ ] | -| [CICD_RUNNER-2] Dart runner reporting client | [ ] | +| [CICD_RUNNER-1] runner job proto 계약 | [x] | +| [CICD_RUNNER-2] Dart runner reporting client | [x] | ## 구현 체크리스트 -- [ ] `proto/oto/runner.proto`에 runner job claim/reporting 최소 메시지를 추가하고 `make proto`로 Go/Dart 생성물을 갱신한다. -- [ ] Dart runner client에 job claim, execution report, log/artifact report helper를 추가한다. -- [ ] `DefaultAgentRunner` 또는 OTO Server session 흐름에 polling/reporting을 주입 가능한 형태로 연결한다. -- [ ] fake HTTP server 테스트로 runner가 job을 받고 BuildResult/stepEvents를 OTO Server report payload로 보내는지 검증한다. -- [ ] `make proto`, `cd services/core && go test -count=1 ./...`, `cd apps/runner && dart analyze`, agent-smoke Dart 테스트를 통과시킨다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. +- [x] `proto/oto/runner.proto`에 runner job claim/reporting 최소 메시지를 추가하고 `make proto`로 Go/Dart 생성물을 갱신한다. +- [x] Dart runner client에 job claim, execution report, log/artifact report helper를 추가한다. +- [x] `DefaultAgentRunner` 또는 OTO Server session 흐름에 polling/reporting을 주입 가능한 형태로 연결한다. +- [x] fake HTTP server 테스트로 runner가 job을 받고 BuildResult/stepEvents를 OTO Server report payload로 보내는지 검증한다. +- [x] `make proto`, `cd services/core && go test -count=1 ./...`, `cd apps/runner && dart analyze`, agent-smoke Dart 테스트를 통과시킨다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## 코드리뷰 전용 체크리스트 > **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] active files를 log로 아카이브하고 PASS이면 `complete.log`를 작성한다. -- [ ] PASS이면 active task directory를 archive로 이동한다. +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] active files를 log로 아카이브하고 PASS이면 `complete.log`를 작성한다. +- [x] PASS이면 active task directory를 archive로 이동한다. ## 계획 대비 변경 사항 -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ +- 계획은 Dart runner client와 proto 중심이었으나, runner-facing HTTP 계약을 실제 Go OTO Server 상태 저장소에 남기기 위해 `services/core/internal/httpserver/server.go`에 runner claim/report/log/artifact route를 함께 추가했다. +- `DefaultAgentRunner`에는 실제 polling loop를 고정 구현하지 않고 `runJobs` hook을 추가했다. 현재 runner 실행을 장시간 job loop에 묶지 않으면서 후속 polling/reporting 구현을 주입할 수 있게 하기 위한 최소 연결이다. ## 주요 설계 결정 -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ +- `proto/oto/runner.proto`에 `JobClaimRequest/Response`, `ExecutionReportRequest/Response`, `LogAppendRequest/Response`, `ArtifactReportRequest/Response`, `StepEventReport`를 추가하고 `make proto`로 Go/Dart 생성물을 갱신했다. +- Go Core는 `/api/v1/runners/{runner_id}/jobs/claim`, `/api/v1/runners/{runner_id}/executions/{execution_id}/report`, `/logs`, `/artifacts`를 `cicdstate.Store`와 연결한다. claim은 queued job을 running execution으로 전환하고, report는 execution/job을 succeeded 또는 failed로 전환한다. +- Dart runner에는 `OtoServerJobClient`를 추가해 claim, execution report, log append, artifact report payload를 전송한다. `ExecutionReportRequest` payload는 `BuildResult.toJson()`과 `StepEvent.toJson()`에서 나온 `execution_result`, `step_events`를 포함한다. +- `OtoServerRegistrationClient.openSession()`의 OTO Server session은 `OtoServerJobSession.jobs`로 shared HTTP client 기반 job client를 노출한다. ## 사용자 리뷰 요청 @@ -78,26 +82,53 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 ### CICD_RUNNER-1 중간 검증 ```bash $ make proto -(output) +mkdir -p services/core/oto +protoc -I proto --go_out=services/core --go_opt=paths=source_relative proto/oto/runner.proto +mkdir -p apps/runner/lib/oto/agent +protoc -I proto --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto ``` ### CICD_RUNNER-2 중간 검증 ```bash $ cd apps/runner && dart test test/oto_agent_registration_test.dart -(output) +All tests passed! ``` ### 최종 검증 ```bash $ make proto -(output) +mkdir -p services/core/oto +protoc -I proto --go_out=services/core --go_opt=paths=source_relative proto/oto/runner.proto +mkdir -p apps/runner/lib/oto/agent +protoc -I proto --dart_out=apps/runner/lib/oto/agent proto/oto/runner.proto + $ cd services/core && go test -count=1 ./... -(output) +? github.com/toki/oto/services/core/cmd/oto-core [no test files] +ok github.com/toki/oto/services/core/internal/cicdstate 0.002s +ok github.com/toki/oto/services/core/internal/httpserver 0.005s +ok github.com/toki/oto/services/core/internal/runnerregistry 0.002s +? github.com/toki/oto/services/core/oto [no test files] + $ cd apps/runner && dart analyze -(output) +Analyzing runner... +No issues found! + $ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart -(output) +All tests passed! ``` ## 코드리뷰 결과 +### Review 0 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS이므로 active plan/review를 log로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/complete.log b/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/complete.log new file mode 100644 index 0000000..34973a1 --- /dev/null +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/complete.log @@ -0,0 +1,37 @@ +# Complete - m-control-plane-separation-migration/06+05_runner_dispatch_contract + +## 완료 일시 + +2026-06-06 + +## 요약 + +runner job claim/report 계약 구현을 1회 리뷰 루프로 완료했다. 최종 판정은 PASS다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | proto/Go/Dart runner job 계약, Dart reporting client, Go runner-facing 상태 route, fake server 테스트와 필수 검증 완료 | + +## 구현/정리 내용 + +- `proto/oto/runner.proto`에 job claim, execution report, log append, artifact report 메시지를 추가하고 Go/Dart protobuf 생성물을 갱신했다. +- Go Core에 runner-facing `/api/v1/runners/{runner_id}/jobs/claim`, execution report, log, artifact route를 추가해 `cicdstate.Store`의 job/execution/log/artifact 상태와 연결했다. +- Dart runner에 `OtoServerJobClient`를 추가하고 OTO Server session 및 `DefaultAgentRunner.runJobs` hook으로 주입 가능한 reporting 흐름을 마련했다. +- fake HTTP server 기반 Dart 테스트와 Go handler 테스트를 추가해 claim/report payload와 저장 상태 전이를 검증했다. + +## 최종 검증 + +- `make proto` - PASS; Go/Dart protobuf 생성 성공. +- `cd services/core && go test -count=1 ./...` - PASS; core 전체 package 테스트 통과. +- `cd apps/runner && dart analyze` - PASS; `No issues found!`. +- `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart` - PASS; `All tests passed!`. + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-control-plane-separation-migration/06+05_runner_dispatch_contract/PLAN-cloud-G07.md b/agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-control-plane-separation-migration/06+05_runner_dispatch_contract/PLAN-cloud-G07.md rename to agent-task/archive/2026/06/m-control-plane-separation-migration/06+05_runner_dispatch_contract/plan_cloud_G07_0.log diff --git a/agent-task/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/CODE_REVIEW-cloud-G07.md b/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/code_review_cloud_G07_0.log similarity index 63% rename from agent-task/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/CODE_REVIEW-cloud-G07.md rename to agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/code_review_cloud_G07_0.log index 5836aaf..707cb06 100644 --- a/agent-task/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/CODE_REVIEW-cloud-G07.md +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/code_review_cloud_G07_0.log @@ -32,32 +32,35 @@ task=m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence, plan=0, | 항목 | 완료 여부 | |------|---------| -| [CICD_SMOKE-1] OTO Server CI/CD ownership smoke | [ ] | +| [CICD_SMOKE-1] OTO Server CI/CD ownership smoke | [x] | ## 구현 체크리스트 -- [ ] `apps/runner/test/oto_server_connection_smoke_test.dart`에 OTO Server job/execution/log/artifact 상태 round trip smoke를 추가한다. -- [ ] smoke가 iop Edge나 iop Control Plane endpoint를 사용하지 않는지 검증한다. -- [ ] Go/Dart 필수 검증을 실행하고 실제 출력을 review stub에 기록한다. -- [ ] PASS 시 `Roadmap Targets`의 `oto-owns-cicd` 완료 근거로 쓸 수 있도록 검증 내용을 명확히 남긴다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. +- [x] `apps/runner/test/oto_server_connection_smoke_test.dart`에 OTO Server job/execution/log/artifact 상태 round trip smoke를 추가한다. +- [x] smoke가 iop Edge나 iop Control Plane endpoint를 사용하지 않는지 검증한다. +- [x] Go/Dart 필수 검증을 실행하고 실제 출력을 review stub에 기록한다. +- [x] PASS 시 `Roadmap Targets`의 `oto-owns-cicd` 완료 근거로 쓸 수 있도록 검증 내용을 명확히 남긴다. +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ## 코드리뷰 전용 체크리스트 > **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] active files를 log로 아카이브하고 PASS이면 `complete.log`를 작성한다. -- [ ] PASS이면 active task directory를 archive로 이동한다. -- [ ] PASS이면 Roadmap Completion 메타데이터에 `oto-owns-cicd`를 포함한다. +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] active files를 log로 아카이브하고 PASS이면 `complete.log`를 작성한다. +- [x] PASS이면 active task directory를 archive로 이동한다. +- [x] PASS이면 Roadmap Completion 메타데이터에 `oto-owns-cicd`를 포함한다. ## 계획 대비 변경 사항 -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ +- 계획 범위와 동일하게 `apps/runner/test/oto_server_connection_smoke_test.dart`에 통합 smoke만 추가했다. +- 06에서 Go handler와 Dart `OtoServerJobClient`가 이미 구현되어 있어 추가 server production 변경은 하지 않았다. ## 주요 설계 결정 -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ +- 새 smoke `OTO Server owns job execution logs and artifacts reported by runner`는 실제 `go run ./cmd/oto-core` 프로세스를 띄운 뒤 Dart `OtoServerRegistrationClient`로 runner를 등록하고, 같은 session의 `OtoServerJobSession.jobs`로 job claim, log append, artifact report, execution report를 수행한다. +- smoke는 OTO Server의 `/api/v1/jobs`, `/api/v1/runners/{runner_id}/...`, `/api/v1/executions/{execution_id}/...`만 사용한다. iop Edge 또는 iop Control Plane endpoint는 사용하지 않는다. +- 완료 증거는 server API 재조회로 확인한다. job/execution `state=succeeded`, `execution_id`, log line, artifact metadata가 OTO Server에 남아야 테스트가 통과한다. ## 사용자 리뷰 요청 @@ -84,18 +87,38 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 ### CICD_SMOKE-1 중간 검증 ```bash $ cd apps/runner && dart test test/oto_server_connection_smoke_test.dart -(output) +All tests passed! ``` ### 최종 검증 ```bash $ cd services/core && go test -count=1 ./... -(output) +? github.com/toki/oto/services/core/cmd/oto-core [no test files] +ok github.com/toki/oto/services/core/internal/cicdstate 0.002s +ok github.com/toki/oto/services/core/internal/httpserver 0.005s +ok github.com/toki/oto/services/core/internal/runnerregistry 0.003s +? github.com/toki/oto/services/core/oto [no test files] + $ cd apps/runner && dart analyze -(output) +Analyzing runner... +No issues found! + $ cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart -(output) +All tests passed! ``` ## 코드리뷰 결과 +### Review 0 + +- 종합 판정: PASS +- 차원별 평가: + - correctness: Pass + - completeness: Pass + - test coverage: Pass + - API contract: Pass + - code quality: Pass + - plan deviation: Pass + - verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: PASS이므로 active plan/review를 log로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다. `complete.log`의 Roadmap Completion에는 `oto-owns-cicd`를 포함한다. diff --git a/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/complete.log b/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/complete.log new file mode 100644 index 0000000..8e15d5a --- /dev/null +++ b/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/complete.log @@ -0,0 +1,44 @@ +# Complete - m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence + +## 완료 일시 + +2026-06-06 + +## 요약 + +OTO Server CI/CD ownership smoke 증거를 1회 리뷰 루프로 완료했다. 최종 판정은 PASS다. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | PASS | 실제 Go OTO Server process와 Dart runner session을 통한 job/execution/log/artifact round trip smoke 검증 완료 | + +## 구현/정리 내용 + +- `apps/runner/test/oto_server_connection_smoke_test.dart`에 `OTO Server owns job execution logs and artifacts reported by runner` smoke를 추가했다. +- smoke는 Go OTO Server 프로세스를 띄우고 Dart runner를 등록한 뒤 `OtoServerJobSession.jobs`로 job claim, log append, artifact report, execution report를 수행한다. +- smoke는 OTO Server API 재조회로 job/execution `succeeded` 상태, execution id, log line, artifact metadata가 서버에 남는지 검증한다. +- smoke는 iop Edge나 iop Control Plane endpoint를 사용하지 않는다. + +## 최종 검증 + +- `cd apps/runner && dart test test/oto_server_connection_smoke_test.dart` - PASS; `All tests passed!`. +- `cd services/core && go test -count=1 ./...` - PASS; core 전체 package 테스트 통과. +- `cd apps/runner && dart analyze` - PASS; `No issues found!`. +- `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart` - PASS; `All tests passed!`. + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md` +- Completed task ids: + - `oto-owns-cicd`: PASS; evidence=`plan_cloud_G07_0.log`, `code_review_cloud_G07_0.log`; verification=`cd apps/runner && dart test test/oto_server_connection_smoke_test.dart`, `cd services/core && go test -count=1 ./...`, `cd apps/runner && dart analyze`, `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_server_connection_smoke_test.dart` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/PLAN-cloud-G07.md b/agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/plan_cloud_G07_0.log similarity index 100% rename from agent-task/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/PLAN-cloud-G07.md rename to agent-task/archive/2026/06/m-control-plane-separation-migration/07+05,06_cicd_smoke_evidence/plan_cloud_G07_0.log diff --git a/apps/runner/lib/oto/agent/agent_runner.dart b/apps/runner/lib/oto/agent/agent_runner.dart index f77afeb..ba1b33d 100644 --- a/apps/runner/lib/oto/agent/agent_runner.dart +++ b/apps/runner/lib/oto/agent/agent_runner.dart @@ -20,14 +20,17 @@ class DefaultAgentRunner implements AgentRunner { final RegistrationClient _client; final void Function(String)? _onLog; final Future Function()? _waitForShutdown; + final Future Function(AgentConfig, EdgeAgentSession)? _runJobs; DefaultAgentRunner({ RegistrationClient? client, void Function(String)? onLog, Future Function()? waitForShutdown, + Future Function(AgentConfig, EdgeAgentSession)? runJobs, }) : _client = client ?? OtoServerRegistrationClient(), _onLog = onLog, - _waitForShutdown = waitForShutdown; + _waitForShutdown = waitForShutdown, + _runJobs = runJobs; @override Future run(AgentConfig config) async { @@ -57,6 +60,10 @@ class DefaultAgentRunner implements AgentRunner { _log('Alias: ${result.alias}'); } + if (_runJobs != null) { + await _runJobs(config, session); + } + _log('Agent connected. Waiting for shutdown signal...'); await _awaitShutdown(); } finally { diff --git a/apps/runner/lib/oto/agent/edge_registration_client.dart b/apps/runner/lib/oto/agent/edge_registration_client.dart index c00e187..d2f3af8 100644 --- a/apps/runner/lib/oto/agent/edge_registration_client.dart +++ b/apps/runner/lib/oto/agent/edge_registration_client.dart @@ -7,6 +7,7 @@ import 'package:proto_socket/proto_socket.dart'; import 'package:oto/oto/agent/agent_config.dart'; import 'package:oto/oto/agent/iop/runtime.pb.dart' as iop; import 'package:oto/oto/agent/oto/runner.pb.dart' as oto; +import 'package:oto/oto/agent/oto_server_job_client.dart'; import 'package:oto/oto/commands/command.dart'; import 'package:oto/oto/commands/command_registry.dart'; @@ -168,6 +169,10 @@ abstract class EdgeAgentSession { Future close(); } +abstract class OtoServerJobSession implements EdgeAgentSession { + OtoServerJobClient get jobs; +} + class _OtoEdgeAgentSession implements EdgeAgentSession { final _OtoIopClient _client; @@ -269,7 +274,9 @@ class OtoServerRegistrationClient extends RegistrationClient { if (uri.host.isEmpty) { throw FormatException('Invalid server url: "$serverUrl"'); } - return uri.replace(path: _joinPath(uri.path, '/api/v1/runners/$runnerId/heartbeat')); + return uri.replace( + path: _joinPath(uri.path, '/api/v1/runners/$runnerId/heartbeat'), + ); } static Uri disconnectUri(String serverUrl, String runnerId) { @@ -281,7 +288,9 @@ class OtoServerRegistrationClient extends RegistrationClient { if (uri.host.isEmpty) { throw FormatException('Invalid server url: "$serverUrl"'); } - return uri.replace(path: _joinPath(uri.path, '/api/v1/runners/$runnerId/disconnect')); + return uri.replace( + path: _joinPath(uri.path, '/api/v1/runners/$runnerId/disconnect'), + ); } static String _joinPath(String basePath, String endpointPath) { @@ -313,7 +322,7 @@ class OtoServerRegistrationClient extends RegistrationClient { } } -class _OtoServerAgentSession implements EdgeAgentSession { +class _OtoServerAgentSession implements OtoServerJobSession { final http.Client _client; final bool _shouldCloseClient; final String _serverUrl; @@ -324,6 +333,9 @@ class _OtoServerAgentSession implements EdgeAgentSession { @override final RegistrationResult result; + @override + late final OtoServerJobClient jobs; + _OtoServerAgentSession( this.result, this._client, @@ -332,8 +344,16 @@ class _OtoServerAgentSession implements EdgeAgentSession { this._runnerId, { required Duration heartbeatInterval, }) { + jobs = OtoServerJobClient( + serverUrl: _serverUrl, + runnerId: _runnerId, + client: _client, + ); if (result.accepted) { - _heartbeatTimer = Timer.periodic(heartbeatInterval, (_) => _sendHeartbeat()); + _heartbeatTimer = Timer.periodic( + heartbeatInterval, + (_) => _sendHeartbeat(), + ); scheduleMicrotask(() => _sendHeartbeat()); } } @@ -341,16 +361,17 @@ class _OtoServerAgentSession implements EdgeAgentSession { Future _sendHeartbeat() async { if (_closed) return; try { - final uri = OtoServerRegistrationClient.heartbeatUri(_serverUrl, _runnerId); + final uri = OtoServerRegistrationClient.heartbeatUri( + _serverUrl, + _runnerId, + ); final body = jsonEncode({ 'runner_id': _runnerId, 'status': 1, // HEARTBEAT_STATUS_HEALTHY }); - await _client.post( - uri, - headers: {'content-type': 'application/json'}, - body: body, - ).timeout(const Duration(seconds: 5)); + await _client + .post(uri, headers: {'content-type': 'application/json'}, body: body) + .timeout(const Duration(seconds: 5)); } catch (_) { // Ignore background errors } @@ -366,11 +387,13 @@ class _OtoServerAgentSession implements EdgeAgentSession { if (result.accepted) { try { - final uri = OtoServerRegistrationClient.disconnectUri(_serverUrl, _runnerId); - await _client.post( - uri, - headers: {'content-type': 'application/json'}, - ).timeout(const Duration(seconds: 5)); + final uri = OtoServerRegistrationClient.disconnectUri( + _serverUrl, + _runnerId, + ); + await _client + .post(uri, headers: {'content-type': 'application/json'}) + .timeout(const Duration(seconds: 5)); } catch (_) { // Ignore disconnect errors } diff --git a/apps/runner/lib/oto/agent/oto/runner.pb.dart b/apps/runner/lib/oto/agent/oto/runner.pb.dart index a6e7a58..c42d59d 100644 --- a/apps/runner/lib/oto/agent/oto/runner.pb.dart +++ b/apps/runner/lib/oto/agent/oto/runner.pb.dart @@ -602,6 +602,873 @@ class BootstrapCommandResponse extends $pb.GeneratedMessage { void clearBootstrapCommand() => $_clearField(1); } +class JobClaimRequest extends $pb.GeneratedMessage { + factory JobClaimRequest({ + $core.String? runnerId, + $core.String? jobId, + $core.String? executionId, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + if (jobId != null) result.jobId = jobId; + if (executionId != null) result.executionId = executionId; + return result; + } + + JobClaimRequest._(); + + factory JobClaimRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory JobClaimRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'JobClaimRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..aOS(2, _omitFieldNames ? '' : 'jobId') + ..aOS(3, _omitFieldNames ? '' : 'executionId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JobClaimRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JobClaimRequest copyWith(void Function(JobClaimRequest) updates) => + super.copyWith((message) => updates(message as JobClaimRequest)) + as JobClaimRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static JobClaimRequest create() => JobClaimRequest._(); + @$core.override + JobClaimRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static JobClaimRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static JobClaimRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get jobId => $_getSZ(1); + @$pb.TagNumber(2) + set jobId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasJobId() => $_has(1); + @$pb.TagNumber(2) + void clearJobId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get executionId => $_getSZ(2); + @$pb.TagNumber(3) + set executionId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasExecutionId() => $_has(2); + @$pb.TagNumber(3) + void clearExecutionId() => $_clearField(3); +} + +class JobClaimResponse extends $pb.GeneratedMessage { + factory JobClaimResponse({ + $core.bool? accepted, + $core.String? errorMessage, + $core.String? jobId, + $core.String? executionId, + $core.String? state, + $core.String? runnerId, + }) { + final result = create(); + if (accepted != null) result.accepted = accepted; + if (errorMessage != null) result.errorMessage = errorMessage; + if (jobId != null) result.jobId = jobId; + if (executionId != null) result.executionId = executionId; + if (state != null) result.state = state; + if (runnerId != null) result.runnerId = runnerId; + return result; + } + + JobClaimResponse._(); + + factory JobClaimResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory JobClaimResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'JobClaimResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accepted') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..aOS(3, _omitFieldNames ? '' : 'jobId') + ..aOS(4, _omitFieldNames ? '' : 'executionId') + ..aOS(5, _omitFieldNames ? '' : 'state') + ..aOS(6, _omitFieldNames ? '' : 'runnerId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JobClaimResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JobClaimResponse copyWith(void Function(JobClaimResponse) updates) => + super.copyWith((message) => updates(message as JobClaimResponse)) + as JobClaimResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static JobClaimResponse create() => JobClaimResponse._(); + @$core.override + JobClaimResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static JobClaimResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static JobClaimResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accepted => $_getBF(0); + @$pb.TagNumber(1) + set accepted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasAccepted() => $_has(0); + @$pb.TagNumber(1) + void clearAccepted() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get jobId => $_getSZ(2); + @$pb.TagNumber(3) + set jobId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasJobId() => $_has(2); + @$pb.TagNumber(3) + void clearJobId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get executionId => $_getSZ(3); + @$pb.TagNumber(4) + set executionId($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasExecutionId() => $_has(3); + @$pb.TagNumber(4) + void clearExecutionId() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get state => $_getSZ(4); + @$pb.TagNumber(5) + set state($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasState() => $_has(4); + @$pb.TagNumber(5) + void clearState() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get runnerId => $_getSZ(5); + @$pb.TagNumber(6) + set runnerId($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasRunnerId() => $_has(5); + @$pb.TagNumber(6) + void clearRunnerId() => $_clearField(6); +} + +class StepEventReport extends $pb.GeneratedMessage { + factory StepEventReport({ + $core.int? stepId, + $core.int? workflowIndex, + $core.String? stepType, + $core.String? event, + $core.String? timestamp, + $core.String? commandId, + $core.String? commandType, + $core.Iterable<$core.MapEntry<$core.String, $core.String>>? error, + }) { + final result = create(); + if (stepId != null) result.stepId = stepId; + if (workflowIndex != null) result.workflowIndex = workflowIndex; + if (stepType != null) result.stepType = stepType; + if (event != null) result.event = event; + if (timestamp != null) result.timestamp = timestamp; + if (commandId != null) result.commandId = commandId; + if (commandType != null) result.commandType = commandType; + if (error != null) result.error.addEntries(error); + return result; + } + + StepEventReport._(); + + factory StepEventReport.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StepEventReport.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StepEventReport', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'stepId') + ..aI(2, _omitFieldNames ? '' : 'workflowIndex') + ..aOS(3, _omitFieldNames ? '' : 'stepType') + ..aOS(4, _omitFieldNames ? '' : 'event') + ..aOS(5, _omitFieldNames ? '' : 'timestamp') + ..aOS(6, _omitFieldNames ? '' : 'commandId') + ..aOS(7, _omitFieldNames ? '' : 'commandType') + ..m<$core.String, $core.String>(8, _omitFieldNames ? '' : 'error', + entryClassName: 'StepEventReport.ErrorEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OS, + packageName: const $pb.PackageName('oto.runner.v1')) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepEventReport clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepEventReport copyWith(void Function(StepEventReport) updates) => + super.copyWith((message) => updates(message as StepEventReport)) + as StepEventReport; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StepEventReport create() => StepEventReport._(); + @$core.override + StepEventReport createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StepEventReport getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StepEventReport? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get stepId => $_getIZ(0); + @$pb.TagNumber(1) + set stepId($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasStepId() => $_has(0); + @$pb.TagNumber(1) + void clearStepId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get workflowIndex => $_getIZ(1); + @$pb.TagNumber(2) + set workflowIndex($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasWorkflowIndex() => $_has(1); + @$pb.TagNumber(2) + void clearWorkflowIndex() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get stepType => $_getSZ(2); + @$pb.TagNumber(3) + set stepType($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasStepType() => $_has(2); + @$pb.TagNumber(3) + void clearStepType() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get event => $_getSZ(3); + @$pb.TagNumber(4) + set event($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasEvent() => $_has(3); + @$pb.TagNumber(4) + void clearEvent() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get timestamp => $_getSZ(4); + @$pb.TagNumber(5) + set timestamp($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasTimestamp() => $_has(4); + @$pb.TagNumber(5) + void clearTimestamp() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get commandId => $_getSZ(5); + @$pb.TagNumber(6) + set commandId($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasCommandId() => $_has(5); + @$pb.TagNumber(6) + void clearCommandId() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get commandType => $_getSZ(6); + @$pb.TagNumber(7) + set commandType($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasCommandType() => $_has(6); + @$pb.TagNumber(7) + void clearCommandType() => $_clearField(7); + + @$pb.TagNumber(8) + $pb.PbMap<$core.String, $core.String> get error => $_getMap(7); +} + +class ExecutionReportRequest extends $pb.GeneratedMessage { + factory ExecutionReportRequest({ + $core.String? runnerId, + $core.String? jobId, + $core.String? executionId, + $core.bool? success, + $core.int? exitCode, + $core.String? message, + $core.Iterable? stepEvents, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + if (jobId != null) result.jobId = jobId; + if (executionId != null) result.executionId = executionId; + if (success != null) result.success = success; + if (exitCode != null) result.exitCode = exitCode; + if (message != null) result.message = message; + if (stepEvents != null) result.stepEvents.addAll(stepEvents); + return result; + } + + ExecutionReportRequest._(); + + factory ExecutionReportRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ExecutionReportRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ExecutionReportRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..aOS(2, _omitFieldNames ? '' : 'jobId') + ..aOS(3, _omitFieldNames ? '' : 'executionId') + ..aOB(4, _omitFieldNames ? '' : 'success') + ..aI(5, _omitFieldNames ? '' : 'exitCode') + ..aOS(6, _omitFieldNames ? '' : 'message') + ..pPM(7, _omitFieldNames ? '' : 'stepEvents', + subBuilder: StepEventReport.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExecutionReportRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExecutionReportRequest copyWith( + void Function(ExecutionReportRequest) updates) => + super.copyWith((message) => updates(message as ExecutionReportRequest)) + as ExecutionReportRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ExecutionReportRequest create() => ExecutionReportRequest._(); + @$core.override + ExecutionReportRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ExecutionReportRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ExecutionReportRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get jobId => $_getSZ(1); + @$pb.TagNumber(2) + set jobId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasJobId() => $_has(1); + @$pb.TagNumber(2) + void clearJobId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get executionId => $_getSZ(2); + @$pb.TagNumber(3) + set executionId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasExecutionId() => $_has(2); + @$pb.TagNumber(3) + void clearExecutionId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.bool get success => $_getBF(3); + @$pb.TagNumber(4) + set success($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasSuccess() => $_has(3); + @$pb.TagNumber(4) + void clearSuccess() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get exitCode => $_getIZ(4); + @$pb.TagNumber(5) + set exitCode($core.int value) => $_setSignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasExitCode() => $_has(4); + @$pb.TagNumber(5) + void clearExitCode() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get message => $_getSZ(5); + @$pb.TagNumber(6) + set message($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasMessage() => $_has(5); + @$pb.TagNumber(6) + void clearMessage() => $_clearField(6); + + @$pb.TagNumber(7) + $pb.PbList get stepEvents => $_getList(6); +} + +class ExecutionReportResponse extends $pb.GeneratedMessage { + factory ExecutionReportResponse({ + $core.bool? accepted, + $core.String? errorMessage, + $core.String? jobId, + $core.String? executionId, + $core.String? state, + $core.String? runnerId, + }) { + final result = create(); + if (accepted != null) result.accepted = accepted; + if (errorMessage != null) result.errorMessage = errorMessage; + if (jobId != null) result.jobId = jobId; + if (executionId != null) result.executionId = executionId; + if (state != null) result.state = state; + if (runnerId != null) result.runnerId = runnerId; + return result; + } + + ExecutionReportResponse._(); + + factory ExecutionReportResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ExecutionReportResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ExecutionReportResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accepted') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..aOS(3, _omitFieldNames ? '' : 'jobId') + ..aOS(4, _omitFieldNames ? '' : 'executionId') + ..aOS(5, _omitFieldNames ? '' : 'state') + ..aOS(6, _omitFieldNames ? '' : 'runnerId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExecutionReportResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ExecutionReportResponse copyWith( + void Function(ExecutionReportResponse) updates) => + super.copyWith((message) => updates(message as ExecutionReportResponse)) + as ExecutionReportResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ExecutionReportResponse create() => ExecutionReportResponse._(); + @$core.override + ExecutionReportResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ExecutionReportResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ExecutionReportResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accepted => $_getBF(0); + @$pb.TagNumber(1) + set accepted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasAccepted() => $_has(0); + @$pb.TagNumber(1) + void clearAccepted() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get jobId => $_getSZ(2); + @$pb.TagNumber(3) + set jobId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasJobId() => $_has(2); + @$pb.TagNumber(3) + void clearJobId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get executionId => $_getSZ(3); + @$pb.TagNumber(4) + set executionId($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasExecutionId() => $_has(3); + @$pb.TagNumber(4) + void clearExecutionId() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get state => $_getSZ(4); + @$pb.TagNumber(5) + set state($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasState() => $_has(4); + @$pb.TagNumber(5) + void clearState() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get runnerId => $_getSZ(5); + @$pb.TagNumber(6) + set runnerId($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasRunnerId() => $_has(5); + @$pb.TagNumber(6) + void clearRunnerId() => $_clearField(6); +} + +class LogAppendRequest extends $pb.GeneratedMessage { + factory LogAppendRequest({ + $core.String? runnerId, + $core.String? executionId, + $core.String? line, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + if (executionId != null) result.executionId = executionId; + if (line != null) result.line = line; + return result; + } + + LogAppendRequest._(); + + factory LogAppendRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LogAppendRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LogAppendRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..aOS(2, _omitFieldNames ? '' : 'executionId') + ..aOS(3, _omitFieldNames ? '' : 'line') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LogAppendRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LogAppendRequest copyWith(void Function(LogAppendRequest) updates) => + super.copyWith((message) => updates(message as LogAppendRequest)) + as LogAppendRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LogAppendRequest create() => LogAppendRequest._(); + @$core.override + LogAppendRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LogAppendRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LogAppendRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get executionId => $_getSZ(1); + @$pb.TagNumber(2) + set executionId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasExecutionId() => $_has(1); + @$pb.TagNumber(2) + void clearExecutionId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get line => $_getSZ(2); + @$pb.TagNumber(3) + set line($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasLine() => $_has(2); + @$pb.TagNumber(3) + void clearLine() => $_clearField(3); +} + +class LogAppendResponse extends $pb.GeneratedMessage { + factory LogAppendResponse({ + $core.bool? accepted, + $core.String? errorMessage, + }) { + final result = create(); + if (accepted != null) result.accepted = accepted; + if (errorMessage != null) result.errorMessage = errorMessage; + return result; + } + + LogAppendResponse._(); + + factory LogAppendResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LogAppendResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LogAppendResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accepted') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LogAppendResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LogAppendResponse copyWith(void Function(LogAppendResponse) updates) => + super.copyWith((message) => updates(message as LogAppendResponse)) + as LogAppendResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LogAppendResponse create() => LogAppendResponse._(); + @$core.override + LogAppendResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LogAppendResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LogAppendResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accepted => $_getBF(0); + @$pb.TagNumber(1) + set accepted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasAccepted() => $_has(0); + @$pb.TagNumber(1) + void clearAccepted() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); +} + +class ArtifactReportRequest extends $pb.GeneratedMessage { + factory ArtifactReportRequest({ + $core.String? runnerId, + $core.String? executionId, + $core.String? name, + $core.String? path, + }) { + final result = create(); + if (runnerId != null) result.runnerId = runnerId; + if (executionId != null) result.executionId = executionId; + if (name != null) result.name = name; + if (path != null) result.path = path; + return result; + } + + ArtifactReportRequest._(); + + factory ArtifactReportRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ArtifactReportRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ArtifactReportRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'runnerId') + ..aOS(2, _omitFieldNames ? '' : 'executionId') + ..aOS(3, _omitFieldNames ? '' : 'name') + ..aOS(4, _omitFieldNames ? '' : 'path') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ArtifactReportRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ArtifactReportRequest copyWith( + void Function(ArtifactReportRequest) updates) => + super.copyWith((message) => updates(message as ArtifactReportRequest)) + as ArtifactReportRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ArtifactReportRequest create() => ArtifactReportRequest._(); + @$core.override + ArtifactReportRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ArtifactReportRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ArtifactReportRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get runnerId => $_getSZ(0); + @$pb.TagNumber(1) + set runnerId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRunnerId() => $_has(0); + @$pb.TagNumber(1) + void clearRunnerId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get executionId => $_getSZ(1); + @$pb.TagNumber(2) + set executionId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasExecutionId() => $_has(1); + @$pb.TagNumber(2) + void clearExecutionId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get name => $_getSZ(2); + @$pb.TagNumber(3) + set name($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasName() => $_has(2); + @$pb.TagNumber(3) + void clearName() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get path => $_getSZ(3); + @$pb.TagNumber(4) + set path($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasPath() => $_has(3); + @$pb.TagNumber(4) + void clearPath() => $_clearField(4); +} + +class ArtifactReportResponse extends $pb.GeneratedMessage { + factory ArtifactReportResponse({ + $core.bool? accepted, + $core.String? errorMessage, + }) { + final result = create(); + if (accepted != null) result.accepted = accepted; + if (errorMessage != null) result.errorMessage = errorMessage; + return result; + } + + ArtifactReportResponse._(); + + factory ArtifactReportResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ArtifactReportResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ArtifactReportResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'oto.runner.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'accepted') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ArtifactReportResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ArtifactReportResponse copyWith( + void Function(ArtifactReportResponse) updates) => + super.copyWith((message) => updates(message as ArtifactReportResponse)) + as ArtifactReportResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ArtifactReportResponse create() => ArtifactReportResponse._(); + @$core.override + ArtifactReportResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ArtifactReportResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ArtifactReportResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get accepted => $_getBF(0); + @$pb.TagNumber(1) + set accepted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasAccepted() => $_has(0); + @$pb.TagNumber(1) + void clearAccepted() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); +} + const $core.bool _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const $core.bool _omitMessageNames = diff --git a/apps/runner/lib/oto/agent/oto/runner.pbjson.dart b/apps/runner/lib/oto/agent/oto/runner.pbjson.dart index a8c3f87..6985a36 100644 --- a/apps/runner/lib/oto/agent/oto/runner.pbjson.dart +++ b/apps/runner/lib/oto/agent/oto/runner.pbjson.dart @@ -180,3 +180,191 @@ final $typed_data.Uint8List bootstrapCommandResponseDescriptor = $convert.base64Decode( 'ChhCb290c3RyYXBDb21tYW5kUmVzcG9uc2USKwoRYm9vdHN0cmFwX2NvbW1hbmQYASABKAlSEG' 'Jvb3RzdHJhcENvbW1hbmQ='); + +@$core.Deprecated('Use jobClaimRequestDescriptor instead') +const JobClaimRequest$json = { + '1': 'JobClaimRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'job_id', '3': 2, '4': 1, '5': 9, '10': 'jobId'}, + {'1': 'execution_id', '3': 3, '4': 1, '5': 9, '10': 'executionId'}, + ], +}; + +/// Descriptor for `JobClaimRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List jobClaimRequestDescriptor = $convert.base64Decode( + 'Cg9Kb2JDbGFpbVJlcXVlc3QSGwoJcnVubmVyX2lkGAEgASgJUghydW5uZXJJZBIVCgZqb2JfaW' + 'QYAiABKAlSBWpvYklkEiEKDGV4ZWN1dGlvbl9pZBgDIAEoCVILZXhlY3V0aW9uSWQ='); + +@$core.Deprecated('Use jobClaimResponseDescriptor instead') +const JobClaimResponse$json = { + '1': 'JobClaimResponse', + '2': [ + {'1': 'accepted', '3': 1, '4': 1, '5': 8, '10': 'accepted'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + {'1': 'job_id', '3': 3, '4': 1, '5': 9, '10': 'jobId'}, + {'1': 'execution_id', '3': 4, '4': 1, '5': 9, '10': 'executionId'}, + {'1': 'state', '3': 5, '4': 1, '5': 9, '10': 'state'}, + {'1': 'runner_id', '3': 6, '4': 1, '5': 9, '10': 'runnerId'}, + ], +}; + +/// Descriptor for `JobClaimResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List jobClaimResponseDescriptor = $convert.base64Decode( + 'ChBKb2JDbGFpbVJlc3BvbnNlEhoKCGFjY2VwdGVkGAEgASgIUghhY2NlcHRlZBIjCg1lcnJvcl' + '9tZXNzYWdlGAIgASgJUgxlcnJvck1lc3NhZ2USFQoGam9iX2lkGAMgASgJUgVqb2JJZBIhCgxl' + 'eGVjdXRpb25faWQYBCABKAlSC2V4ZWN1dGlvbklkEhQKBXN0YXRlGAUgASgJUgVzdGF0ZRIbCg' + 'lydW5uZXJfaWQYBiABKAlSCHJ1bm5lcklk'); + +@$core.Deprecated('Use stepEventReportDescriptor instead') +const StepEventReport$json = { + '1': 'StepEventReport', + '2': [ + {'1': 'step_id', '3': 1, '4': 1, '5': 5, '10': 'stepId'}, + {'1': 'workflow_index', '3': 2, '4': 1, '5': 5, '10': 'workflowIndex'}, + {'1': 'step_type', '3': 3, '4': 1, '5': 9, '10': 'stepType'}, + {'1': 'event', '3': 4, '4': 1, '5': 9, '10': 'event'}, + {'1': 'timestamp', '3': 5, '4': 1, '5': 9, '10': 'timestamp'}, + {'1': 'command_id', '3': 6, '4': 1, '5': 9, '10': 'commandId'}, + {'1': 'command_type', '3': 7, '4': 1, '5': 9, '10': 'commandType'}, + { + '1': 'error', + '3': 8, + '4': 3, + '5': 11, + '6': '.oto.runner.v1.StepEventReport.ErrorEntry', + '10': 'error' + }, + ], + '3': [StepEventReport_ErrorEntry$json], +}; + +@$core.Deprecated('Use stepEventReportDescriptor instead') +const StepEventReport_ErrorEntry$json = { + '1': 'ErrorEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, +}; + +/// Descriptor for `StepEventReport`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List stepEventReportDescriptor = $convert.base64Decode( + 'Cg9TdGVwRXZlbnRSZXBvcnQSFwoHc3RlcF9pZBgBIAEoBVIGc3RlcElkEiUKDndvcmtmbG93X2' + 'luZGV4GAIgASgFUg13b3JrZmxvd0luZGV4EhsKCXN0ZXBfdHlwZRgDIAEoCVIIc3RlcFR5cGUS' + 'FAoFZXZlbnQYBCABKAlSBWV2ZW50EhwKCXRpbWVzdGFtcBgFIAEoCVIJdGltZXN0YW1wEh0KCm' + 'NvbW1hbmRfaWQYBiABKAlSCWNvbW1hbmRJZBIhCgxjb21tYW5kX3R5cGUYByABKAlSC2NvbW1h' + 'bmRUeXBlEj8KBWVycm9yGAggAygLMikub3RvLnJ1bm5lci52MS5TdGVwRXZlbnRSZXBvcnQuRX' + 'Jyb3JFbnRyeVIFZXJyb3IaOAoKRXJyb3JFbnRyeRIQCgNrZXkYASABKAlSA2tleRIUCgV2YWx1' + 'ZRgCIAEoCVIFdmFsdWU6AjgB'); + +@$core.Deprecated('Use executionReportRequestDescriptor instead') +const ExecutionReportRequest$json = { + '1': 'ExecutionReportRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'job_id', '3': 2, '4': 1, '5': 9, '10': 'jobId'}, + {'1': 'execution_id', '3': 3, '4': 1, '5': 9, '10': 'executionId'}, + {'1': 'success', '3': 4, '4': 1, '5': 8, '10': 'success'}, + {'1': 'exit_code', '3': 5, '4': 1, '5': 5, '10': 'exitCode'}, + {'1': 'message', '3': 6, '4': 1, '5': 9, '10': 'message'}, + { + '1': 'step_events', + '3': 7, + '4': 3, + '5': 11, + '6': '.oto.runner.v1.StepEventReport', + '10': 'stepEvents' + }, + ], +}; + +/// Descriptor for `ExecutionReportRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List executionReportRequestDescriptor = $convert.base64Decode( + 'ChZFeGVjdXRpb25SZXBvcnRSZXF1ZXN0EhsKCXJ1bm5lcl9pZBgBIAEoCVIIcnVubmVySWQSFQ' + 'oGam9iX2lkGAIgASgJUgVqb2JJZBIhCgxleGVjdXRpb25faWQYAyABKAlSC2V4ZWN1dGlvbklk' + 'EhgKB3N1Y2Nlc3MYBCABKAhSB3N1Y2Nlc3MSGwoJZXhpdF9jb2RlGAUgASgFUghleGl0Q29kZR' + 'IYCgdtZXNzYWdlGAYgASgJUgdtZXNzYWdlEj8KC3N0ZXBfZXZlbnRzGAcgAygLMh4ub3RvLnJ1' + 'bm5lci52MS5TdGVwRXZlbnRSZXBvcnRSCnN0ZXBFdmVudHM='); + +@$core.Deprecated('Use executionReportResponseDescriptor instead') +const ExecutionReportResponse$json = { + '1': 'ExecutionReportResponse', + '2': [ + {'1': 'accepted', '3': 1, '4': 1, '5': 8, '10': 'accepted'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + {'1': 'job_id', '3': 3, '4': 1, '5': 9, '10': 'jobId'}, + {'1': 'execution_id', '3': 4, '4': 1, '5': 9, '10': 'executionId'}, + {'1': 'state', '3': 5, '4': 1, '5': 9, '10': 'state'}, + {'1': 'runner_id', '3': 6, '4': 1, '5': 9, '10': 'runnerId'}, + ], +}; + +/// Descriptor for `ExecutionReportResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List executionReportResponseDescriptor = $convert.base64Decode( + 'ChdFeGVjdXRpb25SZXBvcnRSZXNwb25zZRIaCghhY2NlcHRlZBgBIAEoCFIIYWNjZXB0ZWQSIw' + 'oNZXJyb3JfbWVzc2FnZRgCIAEoCVIMZXJyb3JNZXNzYWdlEhUKBmpvYl9pZBgDIAEoCVIFam9i' + 'SWQSIQoMZXhlY3V0aW9uX2lkGAQgASgJUgtleGVjdXRpb25JZBIUCgVzdGF0ZRgFIAEoCVIFc3' + 'RhdGUSGwoJcnVubmVyX2lkGAYgASgJUghydW5uZXJJZA=='); + +@$core.Deprecated('Use logAppendRequestDescriptor instead') +const LogAppendRequest$json = { + '1': 'LogAppendRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'execution_id', '3': 2, '4': 1, '5': 9, '10': 'executionId'}, + {'1': 'line', '3': 3, '4': 1, '5': 9, '10': 'line'}, + ], +}; + +/// Descriptor for `LogAppendRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List logAppendRequestDescriptor = $convert.base64Decode( + 'ChBMb2dBcHBlbmRSZXF1ZXN0EhsKCXJ1bm5lcl9pZBgBIAEoCVIIcnVubmVySWQSIQoMZXhlY3' + 'V0aW9uX2lkGAIgASgJUgtleGVjdXRpb25JZBISCgRsaW5lGAMgASgJUgRsaW5l'); + +@$core.Deprecated('Use logAppendResponseDescriptor instead') +const LogAppendResponse$json = { + '1': 'LogAppendResponse', + '2': [ + {'1': 'accepted', '3': 1, '4': 1, '5': 8, '10': 'accepted'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + ], +}; + +/// Descriptor for `LogAppendResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List logAppendResponseDescriptor = $convert.base64Decode( + 'ChFMb2dBcHBlbmRSZXNwb25zZRIaCghhY2NlcHRlZBgBIAEoCFIIYWNjZXB0ZWQSIwoNZXJyb3' + 'JfbWVzc2FnZRgCIAEoCVIMZXJyb3JNZXNzYWdl'); + +@$core.Deprecated('Use artifactReportRequestDescriptor instead') +const ArtifactReportRequest$json = { + '1': 'ArtifactReportRequest', + '2': [ + {'1': 'runner_id', '3': 1, '4': 1, '5': 9, '10': 'runnerId'}, + {'1': 'execution_id', '3': 2, '4': 1, '5': 9, '10': 'executionId'}, + {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'}, + {'1': 'path', '3': 4, '4': 1, '5': 9, '10': 'path'}, + ], +}; + +/// Descriptor for `ArtifactReportRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List artifactReportRequestDescriptor = $convert.base64Decode( + 'ChVBcnRpZmFjdFJlcG9ydFJlcXVlc3QSGwoJcnVubmVyX2lkGAEgASgJUghydW5uZXJJZBIhCg' + 'xleGVjdXRpb25faWQYAiABKAlSC2V4ZWN1dGlvbklkEhIKBG5hbWUYAyABKAlSBG5hbWUSEgoE' + 'cGF0aBgEIAEoCVIEcGF0aA=='); + +@$core.Deprecated('Use artifactReportResponseDescriptor instead') +const ArtifactReportResponse$json = { + '1': 'ArtifactReportResponse', + '2': [ + {'1': 'accepted', '3': 1, '4': 1, '5': 8, '10': 'accepted'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + ], +}; + +/// Descriptor for `ArtifactReportResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List artifactReportResponseDescriptor = + $convert.base64Decode( + 'ChZBcnRpZmFjdFJlcG9ydFJlc3BvbnNlEhoKCGFjY2VwdGVkGAEgASgIUghhY2NlcHRlZBIjCg' + '1lcnJvcl9tZXNzYWdlGAIgASgJUgxlcnJvck1lc3NhZ2U='); diff --git a/apps/runner/lib/oto/agent/oto_server_job_client.dart b/apps/runner/lib/oto/agent/oto_server_job_client.dart new file mode 100644 index 0000000..a3bc743 --- /dev/null +++ b/apps/runner/lib/oto/agent/oto_server_job_client.dart @@ -0,0 +1,272 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:oto/oto/agent/oto/runner.pb.dart' as oto; +import 'package:oto/oto/core/build_result.dart'; +import 'package:oto/oto/core/execution_context.dart'; + +class JobClaimResult { + final bool accepted; + final String? errorMessage; + final String jobId; + final String executionId; + final String state; + + const JobClaimResult({ + required this.accepted, + required this.jobId, + required this.executionId, + required this.state, + this.errorMessage, + }); + + factory JobClaimResult.fromJson(Map json) { + return JobClaimResult( + accepted: json['accepted'] == true, + errorMessage: _emptyToNull( + (json['error_message'] ?? json['errorMessage'] ?? '').toString(), + ), + jobId: (json['job_id'] ?? json['jobId'] ?? '').toString(), + executionId: (json['execution_id'] ?? json['executionId'] ?? '') + .toString(), + state: (json['state'] ?? '').toString(), + ); + } +} + +class ExecutionReportResult { + final bool accepted; + final String? errorMessage; + final String jobId; + final String executionId; + final String state; + + const ExecutionReportResult({ + required this.accepted, + required this.jobId, + required this.executionId, + required this.state, + this.errorMessage, + }); + + factory ExecutionReportResult.fromJson(Map json) { + return ExecutionReportResult( + accepted: json['accepted'] == true, + errorMessage: _emptyToNull( + (json['error_message'] ?? json['errorMessage'] ?? '').toString(), + ), + jobId: (json['job_id'] ?? json['jobId'] ?? '').toString(), + executionId: (json['execution_id'] ?? json['executionId'] ?? '') + .toString(), + state: (json['state'] ?? '').toString(), + ); + } +} + +class OtoServerJobClient { + final String serverUrl; + final String runnerId; + final http.Client _client; + final bool _shouldCloseClient; + + OtoServerJobClient({ + required this.serverUrl, + required this.runnerId, + http.Client? client, + }) : _client = client ?? http.Client(), + _shouldCloseClient = client == null; + + Future claimJob({ + required String jobId, + required String executionId, + }) async { + final request = oto.JobClaimRequest() + ..runnerId = runnerId + ..jobId = jobId + ..executionId = executionId; + final response = await _postJson( + _runnerUri('/jobs/claim'), + _jobClaimRequestJson(request), + ); + return JobClaimResult.fromJson(response); + } + + Future reportExecution({ + required String jobId, + required String executionId, + required BuildResult result, + }) async { + final request = oto.ExecutionReportRequest() + ..runnerId = runnerId + ..jobId = jobId + ..executionId = executionId + ..success = result.success + ..exitCode = result.exitCode + ..message = result.message + ..stepEvents.addAll(result.stepEvents.map(_stepEventReport)); + final response = await _postJson( + _runnerUri('/executions/${Uri.encodeComponent(executionId)}/report'), + _executionReportRequestJson(request, result), + ); + return ExecutionReportResult.fromJson(response); + } + + Future appendLog({ + required String executionId, + required String line, + }) async { + final request = oto.LogAppendRequest() + ..runnerId = runnerId + ..executionId = executionId + ..line = line; + final response = await _postJson( + _runnerUri('/executions/${Uri.encodeComponent(executionId)}/logs'), + _logAppendRequestJson(request), + expectedStatus: HttpStatus.created, + ); + if (response['accepted'] != true) { + throw HttpException( + (response['error_message'] ?? + response['errorMessage'] ?? + 'log rejected') + .toString(), + ); + } + } + + Future reportArtifact({ + required String executionId, + required String name, + required String path, + }) async { + final request = oto.ArtifactReportRequest() + ..runnerId = runnerId + ..executionId = executionId + ..name = name + ..path = path; + final response = await _postJson( + _runnerUri('/executions/${Uri.encodeComponent(executionId)}/artifacts'), + _artifactReportRequestJson(request), + expectedStatus: HttpStatus.created, + ); + if (response['accepted'] != true) { + throw HttpException( + (response['error_message'] ?? + response['errorMessage'] ?? + 'artifact rejected') + .toString(), + ); + } + } + + void close() { + if (_shouldCloseClient) { + _client.close(); + } + } + + Uri _runnerUri(String suffix) { + final runnerPath = + '/api/v1/runners/${Uri.encodeComponent(runnerId)}$suffix'; + return _apiUri(serverUrl, runnerPath); + } + + Future> _postJson( + Uri uri, + Map body, { + int expectedStatus = HttpStatus.ok, + }) async { + final response = await _client + .post( + uri, + headers: {'content-type': 'application/json'}, + body: jsonEncode(body), + ) + .timeout(const Duration(seconds: 5)); + if (response.statusCode != expectedStatus) { + throw HttpException( + 'OTO Server request failed with HTTP ${response.statusCode}', + ); + } + final decoded = jsonDecode(response.body); + if (decoded is! Map) { + throw const FormatException('Invalid OTO Server response body'); + } + return decoded; + } +} + +Uri _apiUri(String serverUrl, String endpointPath) { + var raw = serverUrl; + if (!raw.contains('://')) { + raw = 'http://$raw'; + } + final uri = Uri.parse(raw); + if (uri.host.isEmpty) { + throw FormatException('Invalid server url: "$serverUrl"'); + } + return uri.replace(path: _joinPath(uri.path, endpointPath)); +} + +String _joinPath(String basePath, String endpointPath) { + final base = basePath.endsWith('/') + ? basePath.substring(0, basePath.length - 1) + : basePath; + return '$base$endpointPath'; +} + +Map _jobClaimRequestJson(oto.JobClaimRequest request) => { + 'runner_id': request.runnerId, + 'job_id': request.jobId, + 'execution_id': request.executionId, +}; + +Map _executionReportRequestJson( + oto.ExecutionReportRequest request, + BuildResult result, +) { + final resultJson = result.toJson(); + return { + 'runner_id': request.runnerId, + 'job_id': request.jobId, + 'execution_id': request.executionId, + 'success': request.success, + 'exit_code': request.exitCode, + 'message': request.message, + 'step_events': resultJson['stepEvents'], + 'execution_result': resultJson, + }; +} + +Map _logAppendRequestJson(oto.LogAppendRequest request) => { + 'runner_id': request.runnerId, + 'execution_id': request.executionId, + 'line': request.line, +}; + +Map _artifactReportRequestJson( + oto.ArtifactReportRequest request, +) => { + 'runner_id': request.runnerId, + 'execution_id': request.executionId, + 'name': request.name, + 'path': request.path, +}; + +oto.StepEventReport _stepEventReport(StepEvent event) { + final report = oto.StepEventReport() + ..stepId = event.stepId + ..workflowIndex = event.workflowIndex + ..stepType = event.stepType + ..event = event.event + ..timestamp = event.timestamp + ..commandId = event.commandId ?? '' + ..commandType = event.commandType ?? ''; + if (event.error != null) { + report.error.addAll(event.error!); + } + return report; +} + +String? _emptyToNull(String value) => value.isEmpty ? null : value; diff --git a/apps/runner/test/oto_agent_registration_test.dart b/apps/runner/test/oto_agent_registration_test.dart index 061f7c1..f6caacf 100644 --- a/apps/runner/test/oto_agent_registration_test.dart +++ b/apps/runner/test/oto_agent_registration_test.dart @@ -6,6 +6,9 @@ import 'package:test/test.dart'; import 'package:oto/oto/agent/agent_config.dart'; import 'package:oto/oto/agent/agent_runner.dart'; import 'package:oto/oto/agent/edge_registration_client.dart'; +import 'package:oto/oto/agent/oto_server_job_client.dart'; +import 'package:oto/oto/core/build_result.dart'; +import 'package:oto/oto/core/execution_context.dart'; class FakeEdgeAgentSession implements EdgeAgentSession { @override @@ -171,29 +174,30 @@ runtime: final disconnectReceived = Completer(); final serverSub = server.listen((request) async { - if (request.method == 'POST' && request.uri.path == '/api/v1/runners/register') { + if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/register') { registerReceived.complete(); request.response ..headers.contentType = ContentType.json - ..write(jsonEncode({ - 'accepted': true, - 'runner_id': 'agent-123', - 'alias': 'my-agent', - })); - } else if (request.method == 'POST' && request.uri.path == '/api/v1/runners/agent-123/heartbeat') { + ..write( + jsonEncode({ + 'accepted': true, + 'runner_id': 'agent-123', + 'alias': 'my-agent', + }), + ); + } else if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/agent-123/heartbeat') { heartbeatReceived.complete(); request.response ..headers.contentType = ContentType.json - ..write(jsonEncode({ - 'success': true, - })); - } else if (request.method == 'POST' && request.uri.path == '/api/v1/runners/agent-123/disconnect') { + ..write(jsonEncode({'success': true})); + } else if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/agent-123/disconnect') { disconnectReceived.complete(); request.response ..headers.contentType = ContentType.json - ..write(jsonEncode({ - 'success': true, - })); + ..write(jsonEncode({'success': true})); } await request.response.close(); }); @@ -213,6 +217,8 @@ runtime: final session = await client.openSession(config); + expect(session, isA()); + // First heartbeat should be sent automatically await heartbeatReceived.future.timeout(const Duration(seconds: 2)); @@ -227,75 +233,79 @@ runtime: } }); - test('does not send heartbeat or disconnect on rejected registration', () async { - final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); - var heartbeatReceived = false; - var disconnectReceived = false; + test( + 'does not send heartbeat or disconnect on rejected registration', + () async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + var heartbeatReceived = false; + var disconnectReceived = false; - final serverSub = server.listen((request) async { - if (request.method == 'POST' && request.uri.path == '/api/v1/runners/register') { - request.response - ..headers.contentType = ContentType.json - ..write(jsonEncode({ - 'accepted': false, - 'reject_reason': 'Invalid token', - })); - } else if (request.uri.path.contains('heartbeat')) { - heartbeatReceived = true; - request.response - ..headers.contentType = ContentType.json - ..write(jsonEncode({'success': true})); - } else if (request.uri.path.contains('disconnect')) { - disconnectReceived = true; - request.response - ..headers.contentType = ContentType.json - ..write(jsonEncode({'success': true})); + final serverSub = server.listen((request) async { + if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/register') { + request.response + ..headers.contentType = ContentType.json + ..write( + jsonEncode({ + 'accepted': false, + 'reject_reason': 'Invalid token', + }), + ); + } else if (request.uri.path.contains('heartbeat')) { + heartbeatReceived = true; + request.response + ..headers.contentType = ContentType.json + ..write(jsonEncode({'success': true})); + } else if (request.uri.path.contains('disconnect')) { + disconnectReceived = true; + request.response + ..headers.contentType = ContentType.json + ..write(jsonEncode({'success': true})); + } + await request.response.close(); + }); + + try { + final config = AgentConfig( + agent: validConfig.agent, + server: ServerConnectionConfig( + url: 'http://${server.address.host}:${server.port}', + ), + runtime: validConfig.runtime, + ); + final client = OtoServerRegistrationClient( + commandTypes: ['Shell', 'Git'], + heartbeatInterval: const Duration(milliseconds: 50), + ); + + final session = await client.openSession(config); + expect(session.result.accepted, isFalse); + + // Wait a short duration to ensure no heartbeat is sent + await Future.delayed(const Duration(milliseconds: 200)); + expect(heartbeatReceived, isFalse); + + await session.close(); + // Wait a short duration to ensure no disconnect is sent + await Future.delayed(const Duration(milliseconds: 100)); + expect(disconnectReceived, isFalse); + } finally { + await serverSub.cancel(); + await server.close(force: true); } - await request.response.close(); - }); - - try { - final config = AgentConfig( - agent: validConfig.agent, - server: ServerConnectionConfig( - url: 'http://${server.address.host}:${server.port}', - ), - runtime: validConfig.runtime, - ); - final client = OtoServerRegistrationClient( - commandTypes: ['Shell', 'Git'], - heartbeatInterval: const Duration(milliseconds: 50), - ); - - final session = await client.openSession(config); - expect(session.result.accepted, isFalse); - - // Wait a short duration to ensure no heartbeat is sent - await Future.delayed(const Duration(milliseconds: 200)); - expect(heartbeatReceived, isFalse); - - await session.close(); - // Wait a short duration to ensure no disconnect is sent - await Future.delayed(const Duration(milliseconds: 100)); - expect(disconnectReceived, isFalse); - } finally { - await serverSub.cancel(); - await server.close(force: true); - } - }); + }, + ); test('session close cancels future heartbeats', () async { final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); var heartbeatCount = 0; final serverSub = server.listen((request) async { - if (request.method == 'POST' && request.uri.path == '/api/v1/runners/register') { + if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/register') { request.response ..headers.contentType = ContentType.json - ..write(jsonEncode({ - 'accepted': true, - 'runner_id': 'agent-123', - })); + ..write(jsonEncode({'accepted': true, 'runner_id': 'agent-123'})); } else if (request.uri.path.contains('heartbeat')) { heartbeatCount++; request.response @@ -339,6 +349,134 @@ runtime: await server.close(force: true); } }); + + test('job client claims jobs and reports build results', () async { + final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); + final claimReceived = Completer>(); + final reportReceived = Completer>(); + final logReceived = Completer>(); + final artifactReceived = Completer>(); + + final serverSub = server.listen((request) async { + final bodyText = await utf8.decoder.bind(request).join(); + final body = bodyText.isEmpty + ? {} + : jsonDecode(bodyText) as Map; + request.response.headers.contentType = ContentType.json; + if (request.method == 'POST' && + request.uri.path == '/api/v1/runners/agent-123/jobs/claim') { + claimReceived.complete(body); + request.response.write( + jsonEncode({ + 'accepted': true, + 'runner_id': 'agent-123', + 'job_id': 'job-123', + 'execution_id': 'exec-123', + 'state': 'running', + }), + ); + } else if (request.method == 'POST' && + request.uri.path == + '/api/v1/runners/agent-123/executions/exec-123/report') { + reportReceived.complete(body); + request.response.write( + jsonEncode({ + 'accepted': true, + 'runner_id': 'agent-123', + 'job_id': 'job-123', + 'execution_id': 'exec-123', + 'state': 'succeeded', + }), + ); + } else if (request.method == 'POST' && + request.uri.path == + '/api/v1/runners/agent-123/executions/exec-123/logs') { + logReceived.complete(body); + request.response.statusCode = HttpStatus.created; + request.response.write(jsonEncode({'accepted': true})); + } else if (request.method == 'POST' && + request.uri.path == + '/api/v1/runners/agent-123/executions/exec-123/artifacts') { + artifactReceived.complete(body); + request.response.statusCode = HttpStatus.created; + request.response.write(jsonEncode({'accepted': true})); + } else { + request.response.statusCode = HttpStatus.notFound; + request.response.write(jsonEncode({'error': 'not found'})); + } + await request.response.close(); + }); + + final client = OtoServerJobClient( + serverUrl: 'http://${server.address.host}:${server.port}', + runnerId: 'agent-123', + ); + + try { + final claim = await client.claimJob( + jobId: 'job-123', + executionId: 'exec-123', + ); + expect(claim.accepted, isTrue); + expect(claim.state, 'running'); + + final result = BuildResult.success( + stepEvents: [ + StepEvent( + stepId: 0, + workflowIndex: 0, + stepType: 'command', + event: 'completed', + timestamp: '2026-06-05T12:00:00Z', + commandId: 'build', + commandType: 'Shell', + ), + ], + ); + final report = await client.reportExecution( + jobId: 'job-123', + executionId: 'exec-123', + result: result, + ); + expect(report.accepted, isTrue); + expect(report.state, 'succeeded'); + + await client.appendLog( + executionId: 'exec-123', + line: 'build completed', + ); + await client.reportArtifact( + executionId: 'exec-123', + name: 'binary', + path: '/dist/app', + ); + + final claimPayload = await claimReceived.future; + expect(claimPayload['runner_id'], 'agent-123'); + expect(claimPayload['job_id'], 'job-123'); + expect(claimPayload['execution_id'], 'exec-123'); + + final reportPayload = await reportReceived.future; + expect(reportPayload['success'], isTrue); + expect(reportPayload['exit_code'], 0); + expect(reportPayload['message'], 'Build completed successfully.'); + expect(reportPayload['execution_result'], isA>()); + final stepEvents = reportPayload['step_events'] as List; + expect(stepEvents, hasLength(1)); + expect(stepEvents.first, containsPair('commandId', 'build')); + + final logPayload = await logReceived.future; + expect(logPayload['line'], 'build completed'); + + final artifactPayload = await artifactReceived.future; + expect(artifactPayload['name'], 'binary'); + expect(artifactPayload['path'], '/dist/app'); + } finally { + client.close(); + await serverSub.cancel(); + await server.close(force: true); + } + }); }); group('AgentRunner', () { @@ -387,6 +525,30 @@ runtime: expect(fakeClient.lastSession!.closed, isTrue); }); + test('AgentRunner runs injected job loop after registration', () async { + final fakeClient = FakeEdgeRegistrationClient((config) { + return RegistrationResult.accepted('node-123', 'alias-123', { + 'concurrency': 1, + }); + }); + var jobLoopRan = false; + + final runner = DefaultAgentRunner( + client: fakeClient, + onLog: (msg) {}, + runJobs: (config, session) async { + jobLoopRan = true; + expect(config.agent.id, 'agent-123'); + expect(session.result.accepted, isTrue); + }, + waitForShutdown: () async {}, + ); + + await runner.run(validConfig); + + expect(jobLoopRan, isTrue); + }); + test( 'AgentRunner reports rejected registration and closes session', () async { diff --git a/apps/runner/test/oto_server_connection_smoke_test.dart b/apps/runner/test/oto_server_connection_smoke_test.dart index f33b579..25e6623 100644 --- a/apps/runner/test/oto_server_connection_smoke_test.dart +++ b/apps/runner/test/oto_server_connection_smoke_test.dart @@ -6,6 +6,8 @@ import 'package:http/http.dart' as http; import 'package:test/test.dart'; import 'package:oto/oto/agent/agent_config.dart'; import 'package:oto/oto/agent/edge_registration_client.dart'; +import 'package:oto/oto/core/build_result.dart'; +import 'package:oto/oto/core/execution_context.dart'; const _host = '127.0.0.1'; const _token = 'oto-smoke-token'; @@ -342,6 +344,176 @@ fi }, timeout: const Timeout(Duration(seconds: 30)), ); + + test( + 'OTO Server owns job execution logs and artifacts reported by runner', + () async { + final port = await _freePort(); + final serverAddr = '$_host:$port'; + const jobId = 'oto-smoke-job'; + const executionId = 'oto-smoke-execution'; + + final serverProcess = await Process.start( + 'go', + ['run', './cmd/oto-core'], + workingDirectory: '../../services/core', + environment: {'OTO_CORE_ADDR': serverAddr}, + ); + + final output = StringBuffer(); + final stdoutSub = serverProcess.stdout + .transform(systemEncoding.decoder) + .listen(output.write, onError: output.write); + final stderrSub = serverProcess.stderr + .transform(systemEncoding.decoder) + .listen(output.write, onError: output.write); + + try { + await _waitForPort(_host, port, serverProcess, output); + + final agentConfig = AgentConfig( + agent: const AgentIdentityConfig( + id: _runnerId, + alias: _runnerAlias, + enrollmentToken: _token, + ), + server: ServerConnectionConfig(url: 'http://$serverAddr'), + runtime: const AgentRuntimeConfig( + installDir: '/tmp/install', + workspaceRoot: '/tmp/workspace', + logDir: '/tmp/log', + ), + ); + + final registrationClient = OtoServerRegistrationClient( + commandTypes: ['Shell', 'Git'], + heartbeatInterval: const Duration(milliseconds: 200), + ); + final session = await registrationClient.openSession(agentConfig); + try { + expect(session.result.accepted, isTrue); + expect(session, isA()); + final jobs = (session as OtoServerJobSession).jobs; + + final httpClient = http.Client(); + try { + final createJobResponse = await httpClient.post( + Uri.parse('http://$serverAddr/api/v1/jobs'), + headers: {'content-type': 'application/json'}, + body: jsonEncode({'id': jobId, 'name': 'smoke build'}), + ); + expect( + createJobResponse.statusCode, + equals(201), + reason: 'Create job failed: ${createJobResponse.body}', + ); + + final claim = await jobs.claimJob( + jobId: jobId, + executionId: executionId, + ); + expect(claim.accepted, isTrue); + expect(claim.state, 'running'); + + await jobs.appendLog( + executionId: executionId, + line: 'runner started smoke build', + ); + await jobs.reportArtifact( + executionId: executionId, + name: 'smoke-report', + path: '/tmp/oto-smoke/report.txt', + ); + final report = await jobs.reportExecution( + jobId: jobId, + executionId: executionId, + result: BuildResult.success( + stepEvents: [ + StepEvent( + stepId: 0, + workflowIndex: 0, + stepType: 'command', + event: 'started', + timestamp: DateTime.now().toUtc().toIso8601String(), + commandId: 'smoke-build', + commandType: 'Shell', + ), + StepEvent( + stepId: 0, + workflowIndex: 0, + stepType: 'command', + event: 'completed', + timestamp: DateTime.now().toUtc().toIso8601String(), + commandId: 'smoke-build', + commandType: 'Shell', + ), + ], + ), + ); + expect(report.accepted, isTrue); + expect(report.state, 'succeeded'); + + final job = await _getJson( + httpClient, + 'http://$serverAddr/api/v1/jobs/$jobId', + ); + expect(job['state'], 'succeeded'); + expect(job['execution_id'], executionId); + + final execution = await _getJson( + httpClient, + 'http://$serverAddr/api/v1/executions/$executionId', + ); + expect(execution['state'], 'succeeded'); + expect(execution['job_id'], jobId); + + final logs = await _getJson( + httpClient, + 'http://$serverAddr/api/v1/executions/$executionId/logs', + ); + final logLines = (logs['logs'] as List) + .map( + (entry) => + (entry as Map)['Line'] ?? entry['line'], + ) + .map((line) => line.toString()) + .toList(); + expect(logLines, contains('runner started smoke build')); + expect(logLines, contains('Build completed successfully.')); + + final artifacts = await _getJson( + httpClient, + 'http://$serverAddr/api/v1/executions/$executionId/artifacts', + ); + final artifactRows = artifacts['artifacts'] as List; + expect(artifactRows, hasLength(1)); + final artifact = artifactRows.single as Map; + expect(artifact['Name'] ?? artifact['name'], 'smoke-report'); + expect( + artifact['Path'] ?? artifact['path'], + '/tmp/oto-smoke/report.txt', + ); + } finally { + httpClient.close(); + } + } finally { + await session.close(); + } + } finally { + serverProcess.kill(ProcessSignal.sigterm); + await serverProcess.exitCode.timeout( + const Duration(seconds: 5), + onTimeout: () { + serverProcess.kill(ProcessSignal.sigkill); + return serverProcess.exitCode; + }, + ); + await stdoutSub.cancel(); + await stderrSub.cancel(); + } + }, + timeout: const Timeout(Duration(seconds: 30)), + ); } Future _freePort() async { @@ -383,3 +555,9 @@ Future _tryExitCode(Process process) { .timeout(Duration.zero, onTimeout: () => -1) .then((code) => code == -1 ? null : code); } + +Future> _getJson(http.Client client, String url) async { + final response = await client.get(Uri.parse(url)); + expect(response.statusCode, equals(200), reason: response.body); + return jsonDecode(response.body) as Map; +} diff --git a/proto/oto/runner.proto b/proto/oto/runner.proto index 56475dd..6ab5a31 100644 --- a/proto/oto/runner.proto +++ b/proto/oto/runner.proto @@ -53,3 +53,71 @@ message BootstrapCommandRequest { message BootstrapCommandResponse { string bootstrap_command = 1; } + +message JobClaimRequest { + string runner_id = 1; + string job_id = 2; + string execution_id = 3; +} + +message JobClaimResponse { + bool accepted = 1; + string error_message = 2; + string job_id = 3; + string execution_id = 4; + string state = 5; + string runner_id = 6; +} + +message StepEventReport { + int32 step_id = 1; + int32 workflow_index = 2; + string step_type = 3; + string event = 4; + string timestamp = 5; + string command_id = 6; + string command_type = 7; + map error = 8; +} + +message ExecutionReportRequest { + string runner_id = 1; + string job_id = 2; + string execution_id = 3; + bool success = 4; + int32 exit_code = 5; + string message = 6; + repeated StepEventReport step_events = 7; +} + +message ExecutionReportResponse { + bool accepted = 1; + string error_message = 2; + string job_id = 3; + string execution_id = 4; + string state = 5; + string runner_id = 6; +} + +message LogAppendRequest { + string runner_id = 1; + string execution_id = 2; + string line = 3; +} + +message LogAppendResponse { + bool accepted = 1; + string error_message = 2; +} + +message ArtifactReportRequest { + string runner_id = 1; + string execution_id = 2; + string name = 3; + string path = 4; +} + +message ArtifactReportResponse { + bool accepted = 1; + string error_message = 2; +} diff --git a/services/core/internal/httpserver/server.go b/services/core/internal/httpserver/server.go index 671b488..4b6ea3e 100644 --- a/services/core/internal/httpserver/server.go +++ b/services/core/internal/httpserver/server.go @@ -44,7 +44,7 @@ func NewServerWithRegistryAndStore(addr string, registry *runnerregistry.Registr mux.HandleFunc("/api/v1/runners/{id}/disconnect", handleRunnerDisconnect(registry)) mux.HandleFunc("/api/v1/runners/{id}", handleGetRunner(registry)) mux.HandleFunc("/bootstrap/oto-agent.sh", handleServeBootstrapScript()) - mux.HandleFunc("/api/v1/", handleRouter(store, nil)) + mux.HandleFunc("/api/v1/", handleRouter(store, registry)) return &Server{ httpServer: &http.Server{ @@ -453,6 +453,25 @@ func handleRouter(store *cicdstate.Store, registry *runnerregistry.Registry) htt http.NotFound(w, r) } + case "runners": + if len(parts) < 2 { + http.NotFound(w, r) + return + } + runnerID := parts[1] + switch { + case len(parts) == 4 && parts[2] == "jobs" && parts[3] == "claim" && r.Method == http.MethodPost: + handleRunnerClaimJob(store, registry, runnerID)(w, r) + case len(parts) == 5 && parts[2] == "executions" && parts[4] == "report" && r.Method == http.MethodPost: + handleRunnerReportExecution(store, registry, runnerID, parts[3])(w, r) + case len(parts) == 5 && parts[2] == "executions" && parts[4] == "logs" && r.Method == http.MethodPost: + handleRunnerAppendLog(store, registry, runnerID, parts[3])(w, r) + case len(parts) == 5 && parts[2] == "executions" && parts[4] == "artifacts" && r.Method == http.MethodPost: + handleRunnerAppendArtifact(store, registry, runnerID, parts[3])(w, r) + default: + http.NotFound(w, r) + } + default: http.NotFound(w, r) } @@ -669,6 +688,282 @@ func handleGetArtifacts(store *cicdstate.Store) http.HandlerFunc { } } +func handleRunnerClaimJob(store *cicdstate.Store, registry *runnerregistry.Registry, runnerID string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req otopb.JobClaimRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + ErrorMessage: "invalid claim request", + }) + return + } + + if req.RunnerId == "" { + req.RunnerId = runnerID + } else if req.RunnerId != runnerID { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + ErrorMessage: "runner id mismatch between path and body", + }) + return + } + if err := ensureRunnerKnown(registry, runnerID); err != nil { + writeResponse(w, http.StatusNotFound, &otopb.JobClaimResponse{ + RunnerId: runnerID, + ErrorMessage: err.Error(), + }) + return + } + + jobID := strings.TrimSpace(req.GetJobId()) + execID := strings.TrimSpace(req.GetExecutionId()) + if jobID == "" || execID == "" { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + RunnerId: runnerID, + ErrorMessage: "job id and execution id are required", + }) + return + } + job, err := store.GetJob(jobID) + if err != nil { + writeResponse(w, http.StatusNotFound, &otopb.JobClaimResponse{ + RunnerId: runnerID, + JobId: jobID, + ErrorMessage: "job not found", + }) + return + } + if job.State != cicdstate.StateQueued { + writeResponse(w, http.StatusConflict, &otopb.JobClaimResponse{ + RunnerId: runnerID, + JobId: jobID, + ErrorMessage: "job is not queued", + State: job.State, + }) + return + } + if _, err := store.CreateExecution(jobID, execID); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + if err := store.TransitionJob(jobID, cicdstate.StateRunning); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + if err := store.TransitionExecution(execID, cicdstate.StateRunning); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.JobClaimResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + + writeResponse(w, http.StatusOK, &otopb.JobClaimResponse{ + Accepted: true, + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + State: cicdstate.StateRunning, + }) + } +} + +func handleRunnerReportExecution(store *cicdstate.Store, registry *runnerregistry.Registry, runnerID string, execID string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req otopb.ExecutionReportRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + ErrorMessage: "invalid execution report request", + }) + return + } + + if req.RunnerId == "" { + req.RunnerId = runnerID + } else if req.RunnerId != runnerID { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + ErrorMessage: "runner id mismatch between path and body", + }) + return + } + if req.ExecutionId == "" { + req.ExecutionId = execID + } else if req.ExecutionId != execID { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + ErrorMessage: "execution id mismatch between path and body", + }) + return + } + if err := ensureRunnerKnown(registry, runnerID); err != nil { + writeResponse(w, http.StatusNotFound, &otopb.ExecutionReportResponse{ + RunnerId: runnerID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + + exec, err := store.GetExecution(execID) + if err != nil { + writeResponse(w, http.StatusNotFound, &otopb.ExecutionReportResponse{ + RunnerId: runnerID, + ExecutionId: execID, + ErrorMessage: "execution not found", + }) + return + } + jobID := strings.TrimSpace(req.GetJobId()) + if jobID == "" { + jobID = exec.JobID + } else if jobID != exec.JobID { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: "job id mismatch for execution", + }) + return + } + + targetState := cicdstate.StateFailed + if req.GetSuccess() { + targetState = cicdstate.StateSucceeded + } + if err := store.TransitionExecution(execID, targetState); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + if err := store.TransitionJob(jobID, targetState); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.ExecutionReportResponse{ + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + ErrorMessage: err.Error(), + }) + return + } + if strings.TrimSpace(req.GetMessage()) != "" { + _ = store.AppendLog(execID, req.GetMessage()) + } + + writeResponse(w, http.StatusOK, &otopb.ExecutionReportResponse{ + Accepted: true, + RunnerId: runnerID, + JobId: jobID, + ExecutionId: execID, + State: targetState, + }) + } +} + +func handleRunnerAppendLog(store *cicdstate.Store, registry *runnerregistry.Registry, runnerID string, execID string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req otopb.LogAppendRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.LogAppendResponse{ + ErrorMessage: "invalid log append request", + }) + return + } + if err := normalizeRunnerExecutionRequest(registry, runnerID, execID, req.GetRunnerId(), req.GetExecutionId()); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.LogAppendResponse{ + ErrorMessage: err.Error(), + }) + return + } + line := strings.TrimSpace(req.GetLine()) + if line == "" { + writeResponse(w, http.StatusBadRequest, &otopb.LogAppendResponse{ + ErrorMessage: "line is required", + }) + return + } + if err := store.AppendLog(execID, line); err != nil { + writeResponse(w, http.StatusNotFound, &otopb.LogAppendResponse{ + ErrorMessage: "execution not found", + }) + return + } + writeResponse(w, http.StatusCreated, &otopb.LogAppendResponse{ + Accepted: true, + }) + } +} + +func handleRunnerAppendArtifact(store *cicdstate.Store, registry *runnerregistry.Registry, runnerID string, execID string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req otopb.ArtifactReportRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.ArtifactReportResponse{ + ErrorMessage: "invalid artifact report request", + }) + return + } + if err := normalizeRunnerExecutionRequest(registry, runnerID, execID, req.GetRunnerId(), req.GetExecutionId()); err != nil { + writeResponse(w, http.StatusBadRequest, &otopb.ArtifactReportResponse{ + ErrorMessage: err.Error(), + }) + return + } + name := strings.TrimSpace(req.GetName()) + path := strings.TrimSpace(req.GetPath()) + if name == "" || path == "" { + writeResponse(w, http.StatusBadRequest, &otopb.ArtifactReportResponse{ + ErrorMessage: "name and path are required", + }) + return + } + if err := store.AppendArtifact(execID, name, path); err != nil { + writeResponse(w, http.StatusNotFound, &otopb.ArtifactReportResponse{ + ErrorMessage: "execution not found", + }) + return + } + writeResponse(w, http.StatusCreated, &otopb.ArtifactReportResponse{ + Accepted: true, + }) + } +} + +func normalizeRunnerExecutionRequest(registry *runnerregistry.Registry, runnerID string, execID string, bodyRunnerID string, bodyExecID string) error { + if bodyRunnerID != "" && bodyRunnerID != runnerID { + return fmt.Errorf("runner id mismatch between path and body") + } + if bodyExecID != "" && bodyExecID != execID { + return fmt.Errorf("execution id mismatch between path and body") + } + return ensureRunnerKnown(registry, runnerID) +} + +func ensureRunnerKnown(registry *runnerregistry.Registry, runnerID string) error { + if strings.TrimSpace(runnerID) == "" { + return fmt.Errorf("missing runner id") + } + if registry == nil { + return nil + } + if _, ok := registry.Snapshot(runnerID); !ok { + return fmt.Errorf("runner not found") + } + return nil +} + // --- JSON helpers --- type jobResponse struct { diff --git a/services/core/internal/httpserver/server_test.go b/services/core/internal/httpserver/server_test.go index d139a97..fc6e081 100644 --- a/services/core/internal/httpserver/server_test.go +++ b/services/core/internal/httpserver/server_test.go @@ -723,3 +723,132 @@ func TestHandleCicdNotFound(t *testing.T) { t.Fatalf("unknown path = %v, want %v", rr.Code, http.StatusNotFound) } } + +func TestHandleRunnerCicdClaimReportLogsAndArtifacts(t *testing.T) { + store := cicdstate.NewStore() + registry := runnerregistry.New() + registry.Register(&otopb.RegisterRunnerRequest{ + EnrollmentToken: "token-123", + RunnerId: "runner-123", + ProtocolVersion: "oto.runner.v1", + }) + if _, err := store.CreateJob("job-1", "build"); err != nil { + t.Fatalf("CreateJob failed: %v", err) + } + + claimBody := bytes.NewBufferString(`{ + "runner_id":"runner-123", + "job_id":"job-1", + "execution_id":"exec-1" + }`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/runners/runner-123/jobs/claim", claimBody) + rr := httptest.NewRecorder() + handleRouter(store, registry)(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("claim status = %v, want %v; body=%s", rr.Code, http.StatusOK, rr.Body.String()) + } + var claim otopb.JobClaimResponse + if err := json.Unmarshal(rr.Body.Bytes(), &claim); err != nil { + t.Fatalf("decode claim response: %v", err) + } + if !claim.GetAccepted() || claim.GetState() != cicdstate.StateRunning { + t.Fatalf("claim response = %+v, want accepted running", claim) + } + + job, err := store.GetJob("job-1") + if err != nil { + t.Fatalf("GetJob after claim failed: %v", err) + } + if job.State != cicdstate.StateRunning || job.ExecutionID != "exec-1" { + t.Fatalf("job after claim = %+v", job) + } + + logBody := bytes.NewBufferString(`{ + "runner_id":"runner-123", + "execution_id":"exec-1", + "line":"build started" + }`) + req = httptest.NewRequest(http.MethodPost, "/api/v1/runners/runner-123/executions/exec-1/logs", logBody) + rr = httptest.NewRecorder() + handleRouter(store, registry)(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("append log status = %v, want %v; body=%s", rr.Code, http.StatusCreated, rr.Body.String()) + } + + artifactBody := bytes.NewBufferString(`{ + "runner_id":"runner-123", + "execution_id":"exec-1", + "name":"binary", + "path":"/dist/app" + }`) + req = httptest.NewRequest(http.MethodPost, "/api/v1/runners/runner-123/executions/exec-1/artifacts", artifactBody) + rr = httptest.NewRecorder() + handleRouter(store, registry)(rr, req) + if rr.Code != http.StatusCreated { + t.Fatalf("append artifact status = %v, want %v; body=%s", rr.Code, http.StatusCreated, rr.Body.String()) + } + + reportBody := bytes.NewBufferString(`{ + "runner_id":"runner-123", + "job_id":"job-1", + "execution_id":"exec-1", + "success":true, + "exit_code":0, + "message":"Build completed successfully.", + "step_events":[{"event":"completed"}] + }`) + req = httptest.NewRequest(http.MethodPost, "/api/v1/runners/runner-123/executions/exec-1/report", reportBody) + rr = httptest.NewRecorder() + handleRouter(store, registry)(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("report status = %v, want %v; body=%s", rr.Code, http.StatusOK, rr.Body.String()) + } + var report otopb.ExecutionReportResponse + if err := json.Unmarshal(rr.Body.Bytes(), &report); err != nil { + t.Fatalf("decode report response: %v", err) + } + if !report.GetAccepted() || report.GetState() != cicdstate.StateSucceeded { + t.Fatalf("report response = %+v, want accepted succeeded", report) + } + + exec, err := store.GetExecution("exec-1") + if err != nil { + t.Fatalf("GetExecution after report failed: %v", err) + } + if exec.State != cicdstate.StateSucceeded { + t.Fatalf("execution state = %q, want %q", exec.State, cicdstate.StateSucceeded) + } + if len(exec.Logs) != 2 { + t.Fatalf("log count = %d, want explicit log plus report message", len(exec.Logs)) + } + if len(exec.Artifacts) != 1 || exec.Artifacts[0].Name != "binary" { + t.Fatalf("artifacts = %+v, want binary artifact", exec.Artifacts) + } + + job, err = store.GetJob("job-1") + if err != nil { + t.Fatalf("GetJob after report failed: %v", err) + } + if job.State != cicdstate.StateSucceeded { + t.Fatalf("job state = %q, want %q", job.State, cicdstate.StateSucceeded) + } +} + +func TestHandleRunnerCicdRejectsUnknownRunner(t *testing.T) { + store := cicdstate.NewStore() + if _, err := store.CreateJob("job-1", "build"); err != nil { + t.Fatalf("CreateJob failed: %v", err) + } + + body := bytes.NewBufferString(`{ + "runner_id":"missing-runner", + "job_id":"job-1", + "execution_id":"exec-1" + }`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/runners/missing-runner/jobs/claim", body) + rr := httptest.NewRecorder() + handleRouter(store, runnerregistry.New())(rr, req) + if rr.Code != http.StatusNotFound { + t.Fatalf("status = %v, want %v; body=%s", rr.Code, http.StatusNotFound, rr.Body.String()) + } +} diff --git a/services/core/oto/runner.pb.go b/services/core/oto/runner.pb.go index 0c2695c..dab830e 100644 --- a/services/core/oto/runner.pb.go +++ b/services/core/oto/runner.pb.go @@ -518,6 +518,658 @@ func (x *BootstrapCommandResponse) GetBootstrapCommand() string { return "" } +type JobClaimRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + ExecutionId string `protobuf:"bytes,3,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JobClaimRequest) Reset() { + *x = JobClaimRequest{} + mi := &file_oto_runner_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JobClaimRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JobClaimRequest) ProtoMessage() {} + +func (x *JobClaimRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JobClaimRequest.ProtoReflect.Descriptor instead. +func (*JobClaimRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{8} +} + +func (x *JobClaimRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *JobClaimRequest) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *JobClaimRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +type JobClaimResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + JobId string `protobuf:"bytes,3,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + ExecutionId string `protobuf:"bytes,4,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"` + RunnerId string `protobuf:"bytes,6,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JobClaimResponse) Reset() { + *x = JobClaimResponse{} + mi := &file_oto_runner_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JobClaimResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JobClaimResponse) ProtoMessage() {} + +func (x *JobClaimResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JobClaimResponse.ProtoReflect.Descriptor instead. +func (*JobClaimResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{9} +} + +func (x *JobClaimResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *JobClaimResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +func (x *JobClaimResponse) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *JobClaimResponse) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *JobClaimResponse) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *JobClaimResponse) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +type StepEventReport struct { + state protoimpl.MessageState `protogen:"open.v1"` + StepId int32 `protobuf:"varint,1,opt,name=step_id,json=stepId,proto3" json:"step_id,omitempty"` + WorkflowIndex int32 `protobuf:"varint,2,opt,name=workflow_index,json=workflowIndex,proto3" json:"workflow_index,omitempty"` + StepType string `protobuf:"bytes,3,opt,name=step_type,json=stepType,proto3" json:"step_type,omitempty"` + Event string `protobuf:"bytes,4,opt,name=event,proto3" json:"event,omitempty"` + Timestamp string `protobuf:"bytes,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + CommandId string `protobuf:"bytes,6,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + CommandType string `protobuf:"bytes,7,opt,name=command_type,json=commandType,proto3" json:"command_type,omitempty"` + Error map[string]string `protobuf:"bytes,8,rep,name=error,proto3" json:"error,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StepEventReport) Reset() { + *x = StepEventReport{} + mi := &file_oto_runner_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StepEventReport) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StepEventReport) ProtoMessage() {} + +func (x *StepEventReport) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StepEventReport.ProtoReflect.Descriptor instead. +func (*StepEventReport) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{10} +} + +func (x *StepEventReport) GetStepId() int32 { + if x != nil { + return x.StepId + } + return 0 +} + +func (x *StepEventReport) GetWorkflowIndex() int32 { + if x != nil { + return x.WorkflowIndex + } + return 0 +} + +func (x *StepEventReport) GetStepType() string { + if x != nil { + return x.StepType + } + return "" +} + +func (x *StepEventReport) GetEvent() string { + if x != nil { + return x.Event + } + return "" +} + +func (x *StepEventReport) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +func (x *StepEventReport) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *StepEventReport) GetCommandType() string { + if x != nil { + return x.CommandType + } + return "" +} + +func (x *StepEventReport) GetError() map[string]string { + if x != nil { + return x.Error + } + return nil +} + +type ExecutionReportRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + ExecutionId string `protobuf:"bytes,3,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + Success bool `protobuf:"varint,4,opt,name=success,proto3" json:"success,omitempty"` + ExitCode int32 `protobuf:"varint,5,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` + StepEvents []*StepEventReport `protobuf:"bytes,7,rep,name=step_events,json=stepEvents,proto3" json:"step_events,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecutionReportRequest) Reset() { + *x = ExecutionReportRequest{} + mi := &file_oto_runner_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecutionReportRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecutionReportRequest) ProtoMessage() {} + +func (x *ExecutionReportRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecutionReportRequest.ProtoReflect.Descriptor instead. +func (*ExecutionReportRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{11} +} + +func (x *ExecutionReportRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *ExecutionReportRequest) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *ExecutionReportRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *ExecutionReportRequest) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +func (x *ExecutionReportRequest) GetExitCode() int32 { + if x != nil { + return x.ExitCode + } + return 0 +} + +func (x *ExecutionReportRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ExecutionReportRequest) GetStepEvents() []*StepEventReport { + if x != nil { + return x.StepEvents + } + return nil +} + +type ExecutionReportResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + JobId string `protobuf:"bytes,3,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + ExecutionId string `protobuf:"bytes,4,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"` + RunnerId string `protobuf:"bytes,6,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExecutionReportResponse) Reset() { + *x = ExecutionReportResponse{} + mi := &file_oto_runner_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExecutionReportResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecutionReportResponse) ProtoMessage() {} + +func (x *ExecutionReportResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecutionReportResponse.ProtoReflect.Descriptor instead. +func (*ExecutionReportResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{12} +} + +func (x *ExecutionReportResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *ExecutionReportResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +func (x *ExecutionReportResponse) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *ExecutionReportResponse) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *ExecutionReportResponse) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *ExecutionReportResponse) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +type LogAppendRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + ExecutionId string `protobuf:"bytes,2,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + Line string `protobuf:"bytes,3,opt,name=line,proto3" json:"line,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogAppendRequest) Reset() { + *x = LogAppendRequest{} + mi := &file_oto_runner_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogAppendRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogAppendRequest) ProtoMessage() {} + +func (x *LogAppendRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogAppendRequest.ProtoReflect.Descriptor instead. +func (*LogAppendRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{13} +} + +func (x *LogAppendRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *LogAppendRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *LogAppendRequest) GetLine() string { + if x != nil { + return x.Line + } + return "" +} + +type LogAppendResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LogAppendResponse) Reset() { + *x = LogAppendResponse{} + mi := &file_oto_runner_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LogAppendResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LogAppendResponse) ProtoMessage() {} + +func (x *LogAppendResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LogAppendResponse.ProtoReflect.Descriptor instead. +func (*LogAppendResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{14} +} + +func (x *LogAppendResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *LogAppendResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + +type ArtifactReportRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunnerId string `protobuf:"bytes,1,opt,name=runner_id,json=runnerId,proto3" json:"runner_id,omitempty"` + ExecutionId string `protobuf:"bytes,2,opt,name=execution_id,json=executionId,proto3" json:"execution_id,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ArtifactReportRequest) Reset() { + *x = ArtifactReportRequest{} + mi := &file_oto_runner_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ArtifactReportRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArtifactReportRequest) ProtoMessage() {} + +func (x *ArtifactReportRequest) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArtifactReportRequest.ProtoReflect.Descriptor instead. +func (*ArtifactReportRequest) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{15} +} + +func (x *ArtifactReportRequest) GetRunnerId() string { + if x != nil { + return x.RunnerId + } + return "" +} + +func (x *ArtifactReportRequest) GetExecutionId() string { + if x != nil { + return x.ExecutionId + } + return "" +} + +func (x *ArtifactReportRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ArtifactReportRequest) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +type ArtifactReportResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Accepted bool `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"` + ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ArtifactReportResponse) Reset() { + *x = ArtifactReportResponse{} + mi := &file_oto_runner_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ArtifactReportResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ArtifactReportResponse) ProtoMessage() {} + +func (x *ArtifactReportResponse) ProtoReflect() protoreflect.Message { + mi := &file_oto_runner_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ArtifactReportResponse.ProtoReflect.Descriptor instead. +func (*ArtifactReportResponse) Descriptor() ([]byte, []int) { + return file_oto_runner_proto_rawDescGZIP(), []int{16} +} + +func (x *ArtifactReportResponse) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *ArtifactReportResponse) GetErrorMessage() string { + if x != nil { + return x.ErrorMessage + } + return "" +} + var File_oto_runner_proto protoreflect.FileDescriptor const file_oto_runner_proto_rawDesc = "" + @@ -552,7 +1204,63 @@ const file_oto_runner_proto_rawDesc = "" + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12)\n" + "\x10enrollment_token\x18\x02 \x01(\tR\x0fenrollmentToken\"G\n" + "\x18BootstrapCommandResponse\x12+\n" + - "\x11bootstrap_command\x18\x01 \x01(\tR\x10bootstrapCommand*q\n" + + "\x11bootstrap_command\x18\x01 \x01(\tR\x10bootstrapCommand\"h\n" + + "\x0fJobClaimRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12\x15\n" + + "\x06job_id\x18\x02 \x01(\tR\x05jobId\x12!\n" + + "\fexecution_id\x18\x03 \x01(\tR\vexecutionId\"\xc0\x01\n" + + "\x10JobClaimResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12\x15\n" + + "\x06job_id\x18\x03 \x01(\tR\x05jobId\x12!\n" + + "\fexecution_id\x18\x04 \x01(\tR\vexecutionId\x12\x14\n" + + "\x05state\x18\x05 \x01(\tR\x05state\x12\x1b\n" + + "\trunner_id\x18\x06 \x01(\tR\brunnerId\"\xdf\x02\n" + + "\x0fStepEventReport\x12\x17\n" + + "\astep_id\x18\x01 \x01(\x05R\x06stepId\x12%\n" + + "\x0eworkflow_index\x18\x02 \x01(\x05R\rworkflowIndex\x12\x1b\n" + + "\tstep_type\x18\x03 \x01(\tR\bstepType\x12\x14\n" + + "\x05event\x18\x04 \x01(\tR\x05event\x12\x1c\n" + + "\ttimestamp\x18\x05 \x01(\tR\ttimestamp\x12\x1d\n" + + "\n" + + "command_id\x18\x06 \x01(\tR\tcommandId\x12!\n" + + "\fcommand_type\x18\a \x01(\tR\vcommandType\x12?\n" + + "\x05error\x18\b \x03(\v2).oto.runner.v1.StepEventReport.ErrorEntryR\x05error\x1a8\n" + + "\n" + + "ErrorEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x81\x02\n" + + "\x16ExecutionReportRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12\x15\n" + + "\x06job_id\x18\x02 \x01(\tR\x05jobId\x12!\n" + + "\fexecution_id\x18\x03 \x01(\tR\vexecutionId\x12\x18\n" + + "\asuccess\x18\x04 \x01(\bR\asuccess\x12\x1b\n" + + "\texit_code\x18\x05 \x01(\x05R\bexitCode\x12\x18\n" + + "\amessage\x18\x06 \x01(\tR\amessage\x12?\n" + + "\vstep_events\x18\a \x03(\v2\x1e.oto.runner.v1.StepEventReportR\n" + + "stepEvents\"\xc7\x01\n" + + "\x17ExecutionReportResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\x12\x15\n" + + "\x06job_id\x18\x03 \x01(\tR\x05jobId\x12!\n" + + "\fexecution_id\x18\x04 \x01(\tR\vexecutionId\x12\x14\n" + + "\x05state\x18\x05 \x01(\tR\x05state\x12\x1b\n" + + "\trunner_id\x18\x06 \x01(\tR\brunnerId\"f\n" + + "\x10LogAppendRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" + + "\fexecution_id\x18\x02 \x01(\tR\vexecutionId\x12\x12\n" + + "\x04line\x18\x03 \x01(\tR\x04line\"T\n" + + "\x11LogAppendResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"\x7f\n" + + "\x15ArtifactReportRequest\x12\x1b\n" + + "\trunner_id\x18\x01 \x01(\tR\brunnerId\x12!\n" + + "\fexecution_id\x18\x02 \x01(\tR\vexecutionId\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\x12\x12\n" + + "\x04path\x18\x04 \x01(\tR\x04path\"Y\n" + + "\x16ArtifactReportResponse\x12\x1a\n" + + "\baccepted\x18\x01 \x01(\bR\baccepted\x12#\n" + + "\rerror_message\x18\x02 \x01(\tR\ferrorMessage*q\n" + "\x0fHeartbeatStatus\x12 \n" + "\x1cHEARTBEAT_STATUS_UNSPECIFIED\x10\x00\x12\x1c\n" + "\x18HEARTBEAT_STATUS_HEALTHY\x10\x01\x12\x1e\n" + @@ -571,7 +1279,7 @@ func file_oto_runner_proto_rawDescGZIP() []byte { } var file_oto_runner_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_oto_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_oto_runner_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_oto_runner_proto_goTypes = []any{ (HeartbeatStatus)(0), // 0: oto.runner.v1.HeartbeatStatus (*RunnerCapability)(nil), // 1: oto.runner.v1.RunnerCapability @@ -582,16 +1290,28 @@ var file_oto_runner_proto_goTypes = []any{ (*HeartbeatResponse)(nil), // 6: oto.runner.v1.HeartbeatResponse (*BootstrapCommandRequest)(nil), // 7: oto.runner.v1.BootstrapCommandRequest (*BootstrapCommandResponse)(nil), // 8: oto.runner.v1.BootstrapCommandResponse + (*JobClaimRequest)(nil), // 9: oto.runner.v1.JobClaimRequest + (*JobClaimResponse)(nil), // 10: oto.runner.v1.JobClaimResponse + (*StepEventReport)(nil), // 11: oto.runner.v1.StepEventReport + (*ExecutionReportRequest)(nil), // 12: oto.runner.v1.ExecutionReportRequest + (*ExecutionReportResponse)(nil), // 13: oto.runner.v1.ExecutionReportResponse + (*LogAppendRequest)(nil), // 14: oto.runner.v1.LogAppendRequest + (*LogAppendResponse)(nil), // 15: oto.runner.v1.LogAppendResponse + (*ArtifactReportRequest)(nil), // 16: oto.runner.v1.ArtifactReportRequest + (*ArtifactReportResponse)(nil), // 17: oto.runner.v1.ArtifactReportResponse + nil, // 18: oto.runner.v1.StepEventReport.ErrorEntry } var file_oto_runner_proto_depIdxs = []int32{ - 1, // 0: oto.runner.v1.RegisterRunnerRequest.capability:type_name -> oto.runner.v1.RunnerCapability - 2, // 1: oto.runner.v1.RegisterRunnerRequest.command_catalog:type_name -> oto.runner.v1.CommandCatalogSummary - 0, // 2: oto.runner.v1.HeartbeatRequest.status:type_name -> oto.runner.v1.HeartbeatStatus - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 1, // 0: oto.runner.v1.RegisterRunnerRequest.capability:type_name -> oto.runner.v1.RunnerCapability + 2, // 1: oto.runner.v1.RegisterRunnerRequest.command_catalog:type_name -> oto.runner.v1.CommandCatalogSummary + 0, // 2: oto.runner.v1.HeartbeatRequest.status:type_name -> oto.runner.v1.HeartbeatStatus + 18, // 3: oto.runner.v1.StepEventReport.error:type_name -> oto.runner.v1.StepEventReport.ErrorEntry + 11, // 4: oto.runner.v1.ExecutionReportRequest.step_events:type_name -> oto.runner.v1.StepEventReport + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_oto_runner_proto_init() } @@ -605,7 +1325,7 @@ func file_oto_runner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_oto_runner_proto_rawDesc), len(file_oto_runner_proto_rawDesc)), NumEnums: 1, - NumMessages: 8, + NumMessages: 18, NumExtensions: 0, NumServices: 0, },