fix: share workspace runtime worker contracts
This commit is contained in:
Generated
+11
@@ -540,6 +540,7 @@ dependencies = [
|
|||||||
"tokio-tungstenite 0.29.0",
|
"tokio-tungstenite 0.29.0",
|
||||||
"uuid",
|
"uuid",
|
||||||
"workdir",
|
"workdir",
|
||||||
|
"workspace-api",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6135,6 +6136,15 @@ dependencies = [
|
|||||||
"worker",
|
"worker",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "workspace-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"workdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
@@ -6265,6 +6275,7 @@ dependencies = [
|
|||||||
"workdir",
|
"workdir",
|
||||||
"worker",
|
"worker",
|
||||||
"worker-runtime",
|
"worker-runtime",
|
||||||
|
"workspace-api",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ members = [
|
|||||||
"crates/ticket",
|
"crates/ticket",
|
||||||
"crates/merge-request",
|
"crates/merge-request",
|
||||||
"crates/project-record",
|
"crates/project-record",
|
||||||
|
"crates/workspace-api",
|
||||||
"crates/workspace-server",
|
"crates/workspace-server",
|
||||||
"tests/e2e",
|
"tests/e2e",
|
||||||
]
|
]
|
||||||
@@ -57,6 +58,7 @@ default-members = [
|
|||||||
"crates/ticket",
|
"crates/ticket",
|
||||||
"crates/merge-request",
|
"crates/merge-request",
|
||||||
"crates/project-record",
|
"crates/project-record",
|
||||||
|
"crates/workspace-api",
|
||||||
"crates/workspace-server",
|
"crates/workspace-server",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ ticket = { path = "crates/ticket" }
|
|||||||
project-record = { path = "crates/project-record" }
|
project-record = { path = "crates/project-record" }
|
||||||
worker = { path = "crates/worker" }
|
worker = { path = "crates/worker" }
|
||||||
worker-runtime = { path = "crates/worker-runtime" }
|
worker-runtime = { path = "crates/worker-runtime" }
|
||||||
|
workspace-api = { path = "crates/workspace-api" }
|
||||||
yoi-plugin-pdk = { path = "crates/plugin-pdk" }
|
yoi-plugin-pdk = { path = "crates/plugin-pdk" }
|
||||||
yoi = { path = "crates/yoi" }
|
yoi = { path = "crates/yoi" }
|
||||||
protocol = { path = "crates/protocol" }
|
protocol = { path = "crates/protocol" }
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ thiserror = { workspace = true }
|
|||||||
tokio = { workspace = true, features = ["rt", "macros", "net", "io-util", "sync", "time", "process", "fs"] }
|
tokio = { workspace = true, features = ["rt", "macros", "net", "io-util", "sync", "time", "process", "fs"] }
|
||||||
tokio-tungstenite = { workspace = true }
|
tokio-tungstenite = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
workspace-api.workspace = true
|
||||||
workdir = { workspace = true }
|
workdir = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use protocol::stream::{decode_event, encode_method};
|
use protocol::stream::{decode_event, encode_method};
|
||||||
use protocol::{ErrorCode, Event, Method};
|
use protocol::{ErrorCode, Event, Method};
|
||||||
use serde::Deserialize;
|
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_tungstenite::connect_async;
|
use tokio_tungstenite::connect_async;
|
||||||
use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
|
use tokio_tungstenite::tungstenite::Message as TungsteniteMessage;
|
||||||
pub use workdir::workspace::WorkingDirectorySummary as BackendWorkingDirectorySummary;
|
pub use workdir::workspace::WorkingDirectorySummary as BackendWorkingDirectorySummary;
|
||||||
|
pub use workspace_api::{
|
||||||
|
Diagnostic as BackendDiagnostic, DiagnosticSeverity as BackendDiagnosticSeverity,
|
||||||
|
ListResponse as BackendRuntimeListResponse, RuntimeSummary as BackendRuntimeSummary,
|
||||||
|
WorkerCapabilitySummary as BackendWorkerCapabilitySummary,
|
||||||
|
WorkerImplementationSummary as BackendWorkerImplementationSummary,
|
||||||
|
WorkerRestoreResponse as BackendWorkerRestoreResponse,
|
||||||
|
WorkerRestoreResult as BackendWorkerRestoreResult, WorkerSummary as BackendWorkerSummary,
|
||||||
|
WorkerWorkspaceSummary as BackendWorkerWorkspaceSummary,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct BackendRuntimeTarget {
|
pub struct BackendRuntimeTarget {
|
||||||
@@ -93,94 +101,6 @@ impl BackendRuntimeListTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
|
||||||
pub struct BackendRuntimeListResponse<T> {
|
|
||||||
pub workspace_id: String,
|
|
||||||
pub limit: usize,
|
|
||||||
pub items: Vec<T>,
|
|
||||||
pub source: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub diagnostics: Vec<BackendDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendRuntimeSummary {
|
|
||||||
pub runtime_id: String,
|
|
||||||
pub label: String,
|
|
||||||
pub kind: String,
|
|
||||||
pub status: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub host_ids: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub diagnostics: Vec<BackendDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerWorkspaceSummary {
|
|
||||||
pub visibility: String,
|
|
||||||
pub identity: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerImplementationSummary {
|
|
||||||
pub kind: String,
|
|
||||||
pub display_hint: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerCapabilitySummary {
|
|
||||||
pub can_stop: bool,
|
|
||||||
pub can_spawn_followup: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerSummary {
|
|
||||||
pub runtime_id: String,
|
|
||||||
pub worker_id: String,
|
|
||||||
pub resource_key: String,
|
|
||||||
pub host_id: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub display_name: String,
|
|
||||||
pub label: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub profile: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub singleton_key: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
pub workspace: BackendWorkerWorkspaceSummary,
|
|
||||||
pub state: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub last_seen_at: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub pinned: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub retention_state: String,
|
|
||||||
pub implementation: BackendWorkerImplementationSummary,
|
|
||||||
pub capabilities: BackendWorkerCapabilitySummary,
|
|
||||||
#[serde(default)]
|
|
||||||
pub working_directory: Option<BackendWorkingDirectorySummary>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub diagnostics: Vec<BackendDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerRestoreResult {
|
|
||||||
pub state: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub worker: Option<BackendWorkerSummary>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub diagnostics: Vec<BackendDiagnostic>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendWorkerRestoreResponse {
|
|
||||||
pub workspace_id: String,
|
|
||||||
pub runtime_id: String,
|
|
||||||
pub worker_id: String,
|
|
||||||
pub result: BackendWorkerRestoreResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BackendRuntimeClient {
|
pub struct BackendRuntimeClient {
|
||||||
target: BackendRuntimeTarget,
|
target: BackendRuntimeTarget,
|
||||||
@@ -277,7 +197,7 @@ pub async fn list_backend_workers(
|
|||||||
}
|
}
|
||||||
Err(error) => diagnostics.push(BackendDiagnostic {
|
Err(error) => diagnostics.push(BackendDiagnostic {
|
||||||
code: "runtime_worker_list_failed".to_string(),
|
code: "runtime_worker_list_failed".to_string(),
|
||||||
severity: Some("error".to_string()),
|
severity: BackendDiagnosticSeverity::Error,
|
||||||
message: format!(
|
message: format!(
|
||||||
"failed to list workers for runtime {}: {error}",
|
"failed to list workers for runtime {}: {error}",
|
||||||
runtime.runtime_id
|
runtime.runtime_id
|
||||||
@@ -619,14 +539,6 @@ fn percent_encode(input: &str, keep: impl Fn(u8) -> bool) -> String {
|
|||||||
encoded
|
encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct BackendDiagnostic {
|
|
||||||
pub code: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub severity: Option<String>,
|
|
||||||
pub message: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ pub use backend_auth::{
|
|||||||
poll_device_login, start_device_login, wait_for_device_login,
|
poll_device_login, start_device_login, wait_for_device_login,
|
||||||
};
|
};
|
||||||
pub use backend_runtime::{
|
pub use backend_runtime::{
|
||||||
BackendDiagnostic, BackendRuntimeClient, BackendRuntimeClientError, BackendRuntimeListResponse,
|
BackendDiagnostic, BackendDiagnosticSeverity, BackendRuntimeClient, BackendRuntimeClientError,
|
||||||
BackendRuntimeListTarget, BackendRuntimeSummary, BackendRuntimeTarget,
|
BackendRuntimeListResponse, BackendRuntimeListTarget, BackendRuntimeSummary,
|
||||||
BackendWorkerCapabilitySummary, BackendWorkerImplementationSummary,
|
BackendRuntimeTarget, BackendWorkerCapabilitySummary, BackendWorkerImplementationSummary,
|
||||||
BackendWorkerRestoreResponse, BackendWorkerRestoreResult, BackendWorkerSummary,
|
BackendWorkerRestoreResponse, BackendWorkerRestoreResult, BackendWorkerSummary,
|
||||||
BackendWorkerWorkspaceSummary, BackendWorkingDirectorySummary, list_backend_stopped_workers,
|
BackendWorkerWorkspaceSummary, BackendWorkingDirectorySummary, list_backend_stopped_workers,
|
||||||
list_backend_workers, restore_backend_worker,
|
list_backend_workers, restore_backend_worker,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub(crate) async fn run(
|
|||||||
}
|
}
|
||||||
Err(error) => response.diagnostics.push(client::BackendDiagnostic {
|
Err(error) => response.diagnostics.push(client::BackendDiagnostic {
|
||||||
code: "backend_stopped_workers_list_failed".to_string(),
|
code: "backend_stopped_workers_list_failed".to_string(),
|
||||||
severity: Some("error".to_string()),
|
severity: client::BackendDiagnosticSeverity::Error,
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -396,6 +396,7 @@ mod tests {
|
|||||||
workspace: BackendWorkerWorkspaceSummary {
|
workspace: BackendWorkerWorkspaceSummary {
|
||||||
visibility: "workspace".to_string(),
|
visibility: "workspace".to_string(),
|
||||||
identity: "ws".to_string(),
|
identity: "ws".to_string(),
|
||||||
|
workspace_id: Some("ws".to_string()),
|
||||||
},
|
},
|
||||||
state: "running".to_string(),
|
state: "running".to_string(),
|
||||||
last_seen_at: None,
|
last_seen_at: None,
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "workspace-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
workdir.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json.workspace = true
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
//! Shared Workspace HTTP resource contracts.
|
||||||
|
//!
|
||||||
|
//! This crate owns transport DTOs exposed by the Workspace Server and consumed
|
||||||
|
//! by Rust clients. Runtime-internal projections remain in their owning crates;
|
||||||
|
//! callers must explicitly construct these Workspace-authoritative resources.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use workdir::workspace::WorkingDirectorySummary;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum DiagnosticSeverity {
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct Diagnostic {
|
||||||
|
pub code: String,
|
||||||
|
pub severity: DiagnosticSeverity,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct ListResponse<T> {
|
||||||
|
pub workspace_id: String,
|
||||||
|
pub limit: usize,
|
||||||
|
pub items: Vec<T>,
|
||||||
|
pub source: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeSourceKind {
|
||||||
|
EmbeddedWorkerRuntime,
|
||||||
|
RemoteHttp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeSourceStatus {
|
||||||
|
Active,
|
||||||
|
Reserved,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeIdentityAuthority {
|
||||||
|
RuntimeRegistryProjection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeSourceSummary {
|
||||||
|
pub kind: RuntimeSourceKind,
|
||||||
|
pub status: RuntimeSourceStatus,
|
||||||
|
pub identity_authority: RuntimeIdentityAuthority,
|
||||||
|
pub note: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeCapabilitySummary {
|
||||||
|
pub can_list_hosts: bool,
|
||||||
|
pub can_list_workers: bool,
|
||||||
|
pub can_get_worker: bool,
|
||||||
|
pub can_spawn_worker: bool,
|
||||||
|
pub can_stop_worker: bool,
|
||||||
|
pub has_workspace_fs: bool,
|
||||||
|
pub has_shell: bool,
|
||||||
|
pub has_git: bool,
|
||||||
|
pub supports_worktrees: bool,
|
||||||
|
pub supports_backend_internal_tools: bool,
|
||||||
|
pub workspace_scope: String,
|
||||||
|
pub max_workers: usize,
|
||||||
|
pub os: String,
|
||||||
|
pub arch: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeSummary {
|
||||||
|
pub runtime_id: String,
|
||||||
|
pub label: String,
|
||||||
|
pub kind: String,
|
||||||
|
pub status: String,
|
||||||
|
pub source: RuntimeSourceSummary,
|
||||||
|
#[serde(default)]
|
||||||
|
pub host_ids: Vec<String>,
|
||||||
|
pub capabilities: RuntimeCapabilitySummary,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerWorkspaceSummary {
|
||||||
|
pub visibility: String,
|
||||||
|
pub identity: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub workspace_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerImplementationSummary {
|
||||||
|
pub kind: String,
|
||||||
|
pub display_hint: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerCapabilitySummary {
|
||||||
|
pub can_stop: bool,
|
||||||
|
pub can_spawn_followup: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Workspace-authoritative Worker projection.
|
||||||
|
///
|
||||||
|
/// `resource_key` is required here even though Runtime-internal Worker summaries
|
||||||
|
/// do not carry one. The Workspace Server must resolve it from Workspace
|
||||||
|
/// authority before constructing this response.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerSummary {
|
||||||
|
pub runtime_id: String,
|
||||||
|
pub worker_id: String,
|
||||||
|
pub resource_key: String,
|
||||||
|
pub host_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub display_name: String,
|
||||||
|
pub label: String,
|
||||||
|
pub profile: Option<String>,
|
||||||
|
pub singleton_key: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub tags: Vec<String>,
|
||||||
|
pub workspace: WorkerWorkspaceSummary,
|
||||||
|
pub state: String,
|
||||||
|
pub last_seen_at: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pinned: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub retention_state: String,
|
||||||
|
pub implementation: WorkerImplementationSummary,
|
||||||
|
pub capabilities: WorkerCapabilitySummary,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
pub working_directory: Option<WorkingDirectorySummary>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum WorkerOperationState {
|
||||||
|
Accepted,
|
||||||
|
Unsupported,
|
||||||
|
Rejected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerRestoreResult {
|
||||||
|
pub state: WorkerOperationState,
|
||||||
|
pub worker: Option<WorkerSummary>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct WorkerRestoreResponse {
|
||||||
|
pub workspace_id: String,
|
||||||
|
pub runtime_id: String,
|
||||||
|
pub worker_id: String,
|
||||||
|
pub result: WorkerRestoreResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn worker_resource_key_is_required() {
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"runtime_id": "arcadia",
|
||||||
|
"worker_id": "worker-1",
|
||||||
|
"host_id": "host",
|
||||||
|
"display_name": "Coder",
|
||||||
|
"label": "Coder",
|
||||||
|
"workspace": {
|
||||||
|
"visibility": "workspace",
|
||||||
|
"identity": "workspace-test"
|
||||||
|
},
|
||||||
|
"state": "idle",
|
||||||
|
"implementation": {"kind": "worker", "display_hint": "Coder"},
|
||||||
|
"capabilities": {"can_stop": true, "can_spawn_followup": false}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert!(serde_json::from_value::<WorkerSummary>(payload).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ tokio = { workspace = true, features = ["fs", "macros", "net", "rt-multi-thread"
|
|||||||
tower.workspace = true
|
tower.workspace = true
|
||||||
tokio-tungstenite.workspace = true
|
tokio-tungstenite.workspace = true
|
||||||
worker.workspace = true
|
worker.workspace = true
|
||||||
|
workspace-api.workspace = true
|
||||||
workdir = { workspace = true, features = ["http-client"] }
|
workdir = { workspace = true, features = ["http-client"] }
|
||||||
worker-runtime.workspace = true
|
worker-runtime.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
|||||||
@@ -246,8 +246,6 @@ pub struct WorkerCapabilitySummary {
|
|||||||
pub struct WorkerSummary {
|
pub struct WorkerSummary {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub worker: RuntimeWorkerRef,
|
pub worker: RuntimeWorkerRef,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub resource_key: Option<String>,
|
|
||||||
pub host_id: String,
|
pub host_id: String,
|
||||||
/// Human-readable display name. This is not identity and may be duplicated.
|
/// Human-readable display name. This is not identity and may be duplicated.
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
@@ -271,6 +269,119 @@ pub struct WorkerSummary {
|
|||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<RuntimeDiagnostic> for workspace_api::Diagnostic {
|
||||||
|
fn from(diagnostic: RuntimeDiagnostic) -> Self {
|
||||||
|
let severity = match diagnostic.severity {
|
||||||
|
DiagnosticSeverity::Info => workspace_api::DiagnosticSeverity::Info,
|
||||||
|
DiagnosticSeverity::Warning => workspace_api::DiagnosticSeverity::Warning,
|
||||||
|
DiagnosticSeverity::Error => workspace_api::DiagnosticSeverity::Error,
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
code: diagnostic.code,
|
||||||
|
severity,
|
||||||
|
message: diagnostic.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RuntimeSourceSummary> for workspace_api::RuntimeSourceSummary {
|
||||||
|
fn from(source: RuntimeSourceSummary) -> Self {
|
||||||
|
let kind = match source.kind {
|
||||||
|
RuntimeSourceKind::EmbeddedWorkerRuntime => {
|
||||||
|
workspace_api::RuntimeSourceKind::EmbeddedWorkerRuntime
|
||||||
|
}
|
||||||
|
RuntimeSourceKind::RemoteHttp => workspace_api::RuntimeSourceKind::RemoteHttp,
|
||||||
|
};
|
||||||
|
let status = match source.status {
|
||||||
|
RuntimeSourceStatus::Active => workspace_api::RuntimeSourceStatus::Active,
|
||||||
|
RuntimeSourceStatus::Reserved => workspace_api::RuntimeSourceStatus::Reserved,
|
||||||
|
};
|
||||||
|
let identity_authority = match source.identity_authority {
|
||||||
|
RuntimeIdentityAuthority::RuntimeRegistryProjection => {
|
||||||
|
workspace_api::RuntimeIdentityAuthority::RuntimeRegistryProjection
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
status,
|
||||||
|
identity_authority,
|
||||||
|
note: source.note,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RuntimeCapabilitySummary> for workspace_api::RuntimeCapabilitySummary {
|
||||||
|
fn from(capabilities: RuntimeCapabilitySummary) -> Self {
|
||||||
|
Self {
|
||||||
|
can_list_hosts: capabilities.can_list_hosts,
|
||||||
|
can_list_workers: capabilities.can_list_workers,
|
||||||
|
can_get_worker: capabilities.can_get_worker,
|
||||||
|
can_spawn_worker: capabilities.can_spawn_worker,
|
||||||
|
can_stop_worker: capabilities.can_stop_worker,
|
||||||
|
has_workspace_fs: capabilities.has_workspace_fs,
|
||||||
|
has_shell: capabilities.has_shell,
|
||||||
|
has_git: capabilities.has_git,
|
||||||
|
supports_worktrees: capabilities.supports_worktrees,
|
||||||
|
supports_backend_internal_tools: capabilities.supports_backend_internal_tools,
|
||||||
|
workspace_scope: capabilities.workspace_scope,
|
||||||
|
max_workers: capabilities.max_workers,
|
||||||
|
os: capabilities.os,
|
||||||
|
arch: capabilities.arch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RuntimeSummary> for workspace_api::RuntimeSummary {
|
||||||
|
fn from(runtime: RuntimeSummary) -> Self {
|
||||||
|
Self {
|
||||||
|
runtime_id: runtime.runtime_id,
|
||||||
|
label: runtime.label,
|
||||||
|
kind: runtime.kind,
|
||||||
|
status: runtime.status,
|
||||||
|
source: runtime.source.into(),
|
||||||
|
host_ids: runtime.host_ids,
|
||||||
|
capabilities: runtime.capabilities.into(),
|
||||||
|
diagnostics: runtime.diagnostics.into_iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn workspace_worker_summary(
|
||||||
|
summary: WorkerSummary,
|
||||||
|
resource_key: String,
|
||||||
|
) -> workspace_api::WorkerSummary {
|
||||||
|
workspace_api::WorkerSummary {
|
||||||
|
runtime_id: summary.worker.runtime_id,
|
||||||
|
worker_id: summary.worker.worker_id,
|
||||||
|
resource_key,
|
||||||
|
host_id: summary.host_id,
|
||||||
|
display_name: summary.display_name,
|
||||||
|
label: summary.label,
|
||||||
|
profile: summary.profile,
|
||||||
|
singleton_key: summary.singleton_key,
|
||||||
|
tags: summary.tags,
|
||||||
|
workspace: workspace_api::WorkerWorkspaceSummary {
|
||||||
|
visibility: summary.workspace.visibility,
|
||||||
|
identity: summary.workspace.identity,
|
||||||
|
workspace_id: summary.workspace.workspace_id,
|
||||||
|
},
|
||||||
|
state: summary.state,
|
||||||
|
last_seen_at: summary.last_seen_at,
|
||||||
|
pinned: summary.pinned,
|
||||||
|
retention_state: summary.retention_state,
|
||||||
|
implementation: workspace_api::WorkerImplementationSummary {
|
||||||
|
kind: summary.implementation.kind,
|
||||||
|
display_hint: summary.implementation.display_hint,
|
||||||
|
},
|
||||||
|
capabilities: workspace_api::WorkerCapabilitySummary {
|
||||||
|
can_stop: summary.capabilities.can_stop,
|
||||||
|
can_spawn_followup: summary.capabilities.can_spawn_followup,
|
||||||
|
},
|
||||||
|
working_directory: summary.working_directory,
|
||||||
|
diagnostics: summary.diagnostics.into_iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct WorkerRestoreResult {
|
pub struct WorkerRestoreResult {
|
||||||
pub state: WorkerOperationState,
|
pub state: WorkerOperationState,
|
||||||
@@ -509,6 +620,16 @@ pub enum WorkerOperationState {
|
|||||||
Rejected,
|
Rejected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<WorkerOperationState> for workspace_api::WorkerOperationState {
|
||||||
|
fn from(state: WorkerOperationState) -> Self {
|
||||||
|
match state {
|
||||||
|
WorkerOperationState::Accepted => Self::Accepted,
|
||||||
|
WorkerOperationState::Unsupported => Self::Unsupported,
|
||||||
|
WorkerOperationState::Rejected => Self::Rejected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct WorkerSpawnAcceptanceEvidence {
|
pub struct WorkerSpawnAcceptanceEvidence {
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
@@ -1680,7 +1801,6 @@ impl EmbeddedWorkerRuntime {
|
|||||||
);
|
);
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
||||||
resource_key: None,
|
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
@@ -1720,7 +1840,6 @@ impl EmbeddedWorkerRuntime {
|
|||||||
);
|
);
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
||||||
resource_key: None,
|
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
@@ -2806,7 +2925,6 @@ impl RemoteWorkerRuntime {
|
|||||||
);
|
);
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
||||||
resource_key: None,
|
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
@@ -2850,7 +2968,6 @@ impl RemoteWorkerRuntime {
|
|||||||
);
|
);
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
worker: RuntimeWorkerRef::new(&self.runtime_id, worker_id.clone()),
|
||||||
resource_key: None,
|
|
||||||
host_id: self.host_id.clone(),
|
host_id: self.host_id.clone(),
|
||||||
display_name: display.display_name.clone(),
|
display_name: display.display_name.clone(),
|
||||||
label: display.display_name,
|
label: display.display_name,
|
||||||
@@ -4222,7 +4339,6 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
|
|||||||
let host_id = host_id.into();
|
let host_id = host_id.into();
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new("placeholder", "worker-placeholder"),
|
worker: RuntimeWorkerRef::new("placeholder", "worker-placeholder"),
|
||||||
resource_key: None,
|
|
||||||
host_id,
|
host_id,
|
||||||
display_name: "Worker runtime actions are not implemented".to_string(),
|
display_name: "Worker runtime actions are not implemented".to_string(),
|
||||||
label: "Worker runtime actions are not implemented".to_string(),
|
label: "Worker runtime actions are not implemented".to_string(),
|
||||||
@@ -4616,7 +4732,6 @@ mod tests {
|
|||||||
host_id: host_id.to_string(),
|
host_id: host_id.to_string(),
|
||||||
workers: vec![WorkerSummary {
|
workers: vec![WorkerSummary {
|
||||||
worker: RuntimeWorkerRef::new(runtime_id, worker_id),
|
worker: RuntimeWorkerRef::new(runtime_id, worker_id),
|
||||||
resource_key: None,
|
|
||||||
host_id: host_id.to_string(),
|
host_id: host_id.to_string(),
|
||||||
display_name: label.to_string(),
|
display_name: label.to_string(),
|
||||||
label: label.to_string(),
|
label: label.to_string(),
|
||||||
|
|||||||
@@ -77,13 +77,13 @@ use crate::hosts::{
|
|||||||
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EMBEDDED_RUNTIME_ID,
|
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EMBEDDED_RUNTIME_ID,
|
||||||
EmbeddedWorkerRuntime, HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime,
|
EmbeddedWorkerRuntime, HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime,
|
||||||
RuntimeDiagnostic, RuntimeRegistry, RuntimeRegistryError, RuntimeRegistryUnregisterResult,
|
RuntimeDiagnostic, RuntimeRegistry, RuntimeRegistryError, RuntimeRegistryUnregisterResult,
|
||||||
RuntimeSummary, TicketWorkerRole, WorkerCapabilitySummary, WorkerCompletionsRequest,
|
TicketWorkerRole, WorkerCapabilitySummary, WorkerCompletionsRequest, WorkerCompletionsResult,
|
||||||
WorkerCompletionsResult, WorkerControlOperation, WorkerCreateBinding,
|
WorkerControlOperation, WorkerCreateBinding, WorkerImplementationSummary, WorkerInputKind,
|
||||||
WorkerImplementationSummary, WorkerInputKind, WorkerInputRequest, WorkerInputResult,
|
WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, WorkerLifecycleResult,
|
||||||
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState, WorkerRestoreResult,
|
WorkerOperationState, WorkerRestoreResult, WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent,
|
||||||
WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult,
|
WorkerSpawnRequest, WorkerSpawnResult, WorkerSpawnWorkingDirectoryRequest, WorkerSummary,
|
||||||
WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerTicketAssignmentRequest,
|
WorkerTicketAssignmentRequest, WorkerWorkspaceSummary, worker_spawn_create_fingerprint,
|
||||||
WorkerWorkspaceSummary, worker_spawn_create_fingerprint,
|
workspace_worker_summary,
|
||||||
};
|
};
|
||||||
use crate::identity::WorkspaceIdentity;
|
use crate::identity::WorkspaceIdentity;
|
||||||
use crate::memory_backend::execute_memory_backend_operation_with_authority;
|
use crate::memory_backend::execute_memory_backend_operation_with_authority;
|
||||||
@@ -2264,14 +2264,6 @@ enum RuntimeWorkersStatusFilter {
|
|||||||
Stopped,
|
Stopped,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct WorkerRestoreResponse {
|
|
||||||
pub workspace_id: String,
|
|
||||||
#[serde(flatten)]
|
|
||||||
pub worker_ref: RuntimeWorkerRef,
|
|
||||||
pub result: WorkerRestoreResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum CleanupTargetKind {
|
pub enum CleanupTargetKind {
|
||||||
@@ -6534,7 +6526,7 @@ async fn scoped_get_profile_source_archive(
|
|||||||
async fn scoped_list_runtimes(
|
async fn scoped_list_runtimes(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<RuntimeSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::RuntimeSummary>>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
list_runtimes(State(api)).await
|
list_runtimes(State(api)).await
|
||||||
}
|
}
|
||||||
@@ -6722,7 +6714,7 @@ async fn scoped_worker_remove_source_boundary(
|
|||||||
async fn scoped_get_workspace_worker(
|
async fn scoped_get_workspace_worker(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspaceWorkerReferencePath>,
|
AxumPath(path): AxumPath<ScopedWorkspaceWorkerReferencePath>,
|
||||||
) -> ApiResult<Json<WorkerSummary>> {
|
) -> ApiResult<Json<workspace_api::WorkerSummary>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let worker_id = api
|
let worker_id = api
|
||||||
.store
|
.store
|
||||||
@@ -6738,7 +6730,7 @@ async fn scoped_get_workspace_worker(
|
|||||||
workers
|
workers
|
||||||
.items
|
.items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|worker| worker.worker.worker_id == worker_id)
|
.find(|worker| worker.worker_id == worker_id)
|
||||||
.map(Json)
|
.map(Json)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::UnknownWorker {
|
Error::UnknownWorker {
|
||||||
@@ -6751,7 +6743,7 @@ async fn scoped_get_workspace_worker(
|
|||||||
async fn scoped_list_workers(
|
async fn scoped_list_workers(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
list_workers(State(api)).await
|
list_workers(State(api)).await
|
||||||
}
|
}
|
||||||
@@ -7010,7 +7002,7 @@ async fn restore_known_worker(
|
|||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
) -> ApiResult<Json<WorkerRestoreResponse>> {
|
) -> ApiResult<Json<workspace_api::WorkerRestoreResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let source = authenticate_worker_mutation_source(&api, &path.workspace_id, &headers)?;
|
let source = authenticate_worker_mutation_source(&api, &path.workspace_id, &headers)?;
|
||||||
let subject = path.worker.clone();
|
let subject = path.worker.clone();
|
||||||
@@ -7590,7 +7582,7 @@ fn build_runtime_cleanup_plan(
|
|||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|worker| worker.state == "running")
|
.filter(|worker| worker.state == "running")
|
||||||
.map(|worker| worker.worker.clone())
|
.map(|worker| RuntimeWorkerRef::new(&worker.runtime_id, &worker.worker_id))
|
||||||
.collect();
|
.collect();
|
||||||
let (workdir_summaries, mut diagnostics) =
|
let (workdir_summaries, mut diagnostics) =
|
||||||
match runtime_working_directory_summaries(api, runtime_id) {
|
match runtime_working_directory_summaries(api, runtime_id) {
|
||||||
@@ -8107,7 +8099,7 @@ async fn scoped_list_runtime_workers(
|
|||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedRuntimePath>,
|
AxumPath(path): AxumPath<ScopedRuntimePath>,
|
||||||
Query(query): Query<RuntimeWorkersQuery>,
|
Query(query): Query<RuntimeWorkersQuery>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
list_runtime_workers(State(api), AxumPath(path.runtime_id), Query(query)).await
|
list_runtime_workers(State(api), AxumPath(path.runtime_id), Query(query)).await
|
||||||
}
|
}
|
||||||
@@ -8166,7 +8158,7 @@ async fn scoped_restore_runtime_worker(
|
|||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
AxumPath(path): AxumPath<ScopedRuntimeWorkerPath>,
|
||||||
Query(query): Query<RestoreTicketAssignmentQuery>,
|
Query(query): Query<RestoreTicketAssignmentQuery>,
|
||||||
) -> ApiResult<Json<WorkerRestoreResponse>> {
|
) -> ApiResult<Json<workspace_api::WorkerRestoreResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let workspace_id = path.workspace_id.clone();
|
let workspace_id = path.workspace_id.clone();
|
||||||
let runtime_id = path.worker.runtime_id.clone();
|
let runtime_id = path.worker.runtime_id.clone();
|
||||||
@@ -8219,11 +8211,13 @@ async fn scoped_restore_runtime_worker(
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
assign_ticket_worker_from_lifecycle(&api, assignment, &runtime_id, &worker_id)?;
|
assign_ticket_worker_from_lifecycle(&api, assignment, &runtime_id, &worker_id)?;
|
||||||
return Ok(Json(WorkerRestoreResponse {
|
let worker = project_workspace_worker(&api, worker)?;
|
||||||
|
return Ok(Json(workspace_api::WorkerRestoreResponse {
|
||||||
workspace_id,
|
workspace_id,
|
||||||
worker_ref: RuntimeWorkerRef::new(&runtime_id, &worker_id),
|
runtime_id: runtime_id.clone(),
|
||||||
result: crate::hosts::WorkerRestoreResult {
|
worker_id: worker_id.clone(),
|
||||||
state: WorkerOperationState::Accepted,
|
result: workspace_api::WorkerRestoreResult {
|
||||||
|
state: workspace_api::WorkerOperationState::Accepted,
|
||||||
worker: Some(worker),
|
worker: Some(worker),
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
},
|
},
|
||||||
@@ -8352,7 +8346,7 @@ async fn scoped_worker_protocol_ws(
|
|||||||
async fn scoped_list_host_workers(
|
async fn scoped_list_host_workers(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedHostPath>,
|
AxumPath(path): AxumPath<ScopedHostPath>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
list_host_workers(State(api), AxumPath(path.host_id)).await
|
list_host_workers(State(api), AxumPath(path.host_id)).await
|
||||||
}
|
}
|
||||||
@@ -9304,21 +9298,21 @@ async fn list_hosts(
|
|||||||
|
|
||||||
async fn list_runtimes(
|
async fn list_runtimes(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<RuntimeSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::RuntimeSummary>>> {
|
||||||
let limit = api.config.max_records.min(200);
|
let limit = api.config.max_records.min(200);
|
||||||
let runtimes = api.runtime.list_runtimes(limit);
|
let runtimes = api.runtime.list_runtimes(limit);
|
||||||
Ok(Json(RuntimeListResponse {
|
Ok(Json(workspace_api::ListResponse {
|
||||||
workspace_id: api.config.workspace_id,
|
workspace_id: api.config.workspace_id,
|
||||||
limit,
|
limit,
|
||||||
items: runtimes.items,
|
items: runtimes.items.into_iter().map(Into::into).collect(),
|
||||||
source: "worker_runtime_registry".to_string(),
|
source: "worker_runtime_registry".to_string(),
|
||||||
diagnostics: runtimes.diagnostics,
|
diagnostics: runtimes.diagnostics.into_iter().map(Into::into).collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_workers(
|
async fn list_workers(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
workers_response(api).map(Json)
|
workers_response(api).map(Json)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10025,7 +10019,7 @@ async fn post_companion_cancel(
|
|||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct WorkerShowProjection {
|
struct WorkerShowProjection {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
worker: WorkerSummary,
|
worker: workspace_api::WorkerSummary,
|
||||||
updated_at: String,
|
updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10071,31 +10065,18 @@ async fn get_runtime_worker(
|
|||||||
.store
|
.store
|
||||||
.list_workdir_registry(&api.config.workspace_id, 500)?;
|
.list_workdir_registry(&api.config.workspace_id, 500)?;
|
||||||
let updated_at = record.updated_at.clone();
|
let updated_at = record.updated_at.clone();
|
||||||
let mut worker = merge_worker_registry_projection(Some(&worker), &record, links, &workdirs);
|
let worker = merge_worker_registry_projection(Some(&worker), &record, links, &workdirs);
|
||||||
worker.resource_key = Some(
|
let worker = project_workspace_worker(&api, worker)?;
|
||||||
api.store
|
|
||||||
.resource_key(
|
|
||||||
&api.config.workspace_id,
|
|
||||||
WorkspaceResourceKind::Worker,
|
|
||||||
&worker_ref.worker_id,
|
|
||||||
)?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::Store(format!(
|
|
||||||
"Workspace Worker `{}` has no resource key",
|
|
||||||
worker_ref.worker_id
|
|
||||||
))
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
Ok(Json(WorkerShowProjection { worker, updated_at }))
|
Ok(Json(WorkerShowProjection { worker, updated_at }))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn restore_runtime_worker(
|
async fn restore_runtime_worker(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
|
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
|
||||||
) -> ApiResult<Json<WorkerRestoreResponse>> {
|
) -> ApiResult<Json<workspace_api::WorkerRestoreResponse>> {
|
||||||
let worker = resolve_workspace_worker_reference(&api, &runtime_id, &worker_id)?;
|
let worker = resolve_workspace_worker_reference(&api, &runtime_id, &worker_id)?;
|
||||||
let mut result = api.restore_workspace_worker(&worker)?;
|
let result = api.restore_workspace_worker(&worker)?;
|
||||||
if let Some(worker) = result.worker.as_ref() {
|
let projected_worker = if let Some(worker) = result.worker.as_ref() {
|
||||||
let record = sync_worker_observation(&api, worker)?;
|
let record = sync_worker_observation(&api, worker)?;
|
||||||
let links = api
|
let links = api
|
||||||
.store
|
.store
|
||||||
@@ -10103,27 +10084,20 @@ async fn restore_runtime_worker(
|
|||||||
let workdirs = api
|
let workdirs = api
|
||||||
.store
|
.store
|
||||||
.list_workdir_registry(&api.config.workspace_id, 500)?;
|
.list_workdir_registry(&api.config.workspace_id, 500)?;
|
||||||
let mut summary = merge_worker_registry_projection(Some(worker), &record, links, &workdirs);
|
let summary = merge_worker_registry_projection(Some(worker), &record, links, &workdirs);
|
||||||
summary.resource_key = Some(
|
Some(project_workspace_worker(&api, summary)?)
|
||||||
api.store
|
} else {
|
||||||
.resource_key(
|
None
|
||||||
&api.config.workspace_id,
|
};
|
||||||
WorkspaceResourceKind::Worker,
|
Ok(Json(workspace_api::WorkerRestoreResponse {
|
||||||
&record.worker.worker_id,
|
|
||||||
)?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::Store(format!(
|
|
||||||
"Workspace Worker `{}` has no resource key",
|
|
||||||
record.worker.worker_id
|
|
||||||
))
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
result.worker = Some(summary);
|
|
||||||
}
|
|
||||||
Ok(Json(WorkerRestoreResponse {
|
|
||||||
workspace_id: api.workspace_id().to_string(),
|
workspace_id: api.workspace_id().to_string(),
|
||||||
worker_ref: RuntimeWorkerRef::new(&runtime_id, &worker_id),
|
runtime_id: runtime_id.clone(),
|
||||||
result,
|
worker_id: worker_id.clone(),
|
||||||
|
result: workspace_api::WorkerRestoreResult {
|
||||||
|
state: result.state.into(),
|
||||||
|
worker: projected_worker,
|
||||||
|
diagnostics: result.diagnostics.into_iter().map(Into::into).collect(),
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -10179,28 +10153,33 @@ async fn list_runtime_workers(
|
|||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(runtime_id): AxumPath<String>,
|
AxumPath(runtime_id): AxumPath<String>,
|
||||||
Query(query): Query<RuntimeWorkersQuery>,
|
Query(query): Query<RuntimeWorkersQuery>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
let limit = api.config.max_records.min(200);
|
let limit = api.config.max_records.min(200);
|
||||||
let (worker_list, source) = match query.status {
|
let (runtime_workers, source) = match query.status {
|
||||||
Some(RuntimeWorkersStatusFilter::Stopped) => (
|
Some(RuntimeWorkersStatusFilter::Stopped) => (
|
||||||
api.runtime
|
api.runtime
|
||||||
.list_stopped_workers_for_runtime(&runtime_id, limit)
|
.list_stopped_workers_for_runtime(&runtime_id, limit)
|
||||||
.map_err(|err| err.into_error())?,
|
.map_err(|error| error.into_error())?,
|
||||||
"runtime_registry_stopped",
|
"runtime_registry_stopped",
|
||||||
),
|
),
|
||||||
None => (
|
None => (
|
||||||
api.runtime
|
api.runtime
|
||||||
.list_workers_for_runtime(&runtime_id, limit)
|
.list_workers_for_runtime(&runtime_id, limit)
|
||||||
.map_err(|err| err.into_error())?,
|
.map_err(|error| error.into_error())?,
|
||||||
"runtime_registry",
|
"runtime_registry",
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
Ok(Json(RuntimeListResponse {
|
let items = project_observed_workspace_workers(&api, runtime_workers.items)?;
|
||||||
|
Ok(Json(workspace_api::ListResponse {
|
||||||
workspace_id: api.workspace_id().to_string(),
|
workspace_id: api.workspace_id().to_string(),
|
||||||
limit,
|
limit,
|
||||||
items: worker_list.items,
|
items,
|
||||||
source: source.to_string(),
|
source: source.to_string(),
|
||||||
diagnostics: worker_list.diagnostics,
|
diagnostics: runtime_workers
|
||||||
|
.diagnostics
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11142,22 +11121,70 @@ fn protocol_error_event(message: impl Into<String>) -> protocol::Event {
|
|||||||
async fn list_host_workers(
|
async fn list_host_workers(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(host_id): AxumPath<String>,
|
AxumPath(host_id): AxumPath<String>,
|
||||||
) -> ApiResult<Json<RuntimeListResponse<WorkerSummary>>> {
|
) -> ApiResult<Json<workspace_api::ListResponse<workspace_api::WorkerSummary>>> {
|
||||||
let limit = api.config.max_records.min(200);
|
let limit = api.config.max_records.min(200);
|
||||||
let runtime_workers = api
|
let runtime_workers = api
|
||||||
.runtime
|
.runtime
|
||||||
.list_workers_for_host(&host_id, limit)
|
.list_workers_for_host(&host_id, limit)
|
||||||
.map_err(|err| err.into_error())?;
|
.map_err(|err| err.into_error())?;
|
||||||
Ok(Json(RuntimeListResponse {
|
let items = project_observed_workspace_workers(&api, runtime_workers.items)?;
|
||||||
workspace_id: api.config.workspace_id,
|
Ok(Json(workspace_api::ListResponse {
|
||||||
|
workspace_id: api.workspace_id().to_string(),
|
||||||
limit,
|
limit,
|
||||||
items: runtime_workers.items,
|
items,
|
||||||
source: "worker_runtime_registry".to_string(),
|
source: "worker_runtime_registry".to_string(),
|
||||||
diagnostics: runtime_workers.diagnostics,
|
diagnostics: runtime_workers
|
||||||
|
.diagnostics
|
||||||
|
.into_iter()
|
||||||
|
.map(Into::into)
|
||||||
|
.collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workers_response(api: WorkspaceApi) -> ApiResult<RuntimeListResponse<WorkerSummary>> {
|
fn project_workspace_worker(
|
||||||
|
api: &WorkspaceApi,
|
||||||
|
summary: WorkerSummary,
|
||||||
|
) -> ApiResult<workspace_api::WorkerSummary> {
|
||||||
|
let resource_key = api
|
||||||
|
.store
|
||||||
|
.resource_key(
|
||||||
|
&api.config.workspace_id,
|
||||||
|
WorkspaceResourceKind::Worker,
|
||||||
|
&summary.worker.worker_id,
|
||||||
|
)?
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::Store(format!(
|
||||||
|
"Workspace Worker `{}` has no resource key",
|
||||||
|
summary.worker.worker_id
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
Ok(workspace_worker_summary(summary, resource_key))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_observed_workspace_workers(
|
||||||
|
api: &WorkspaceApi,
|
||||||
|
workers: Vec<WorkerSummary>,
|
||||||
|
) -> ApiResult<Vec<workspace_api::WorkerSummary>> {
|
||||||
|
let workdirs = api
|
||||||
|
.store
|
||||||
|
.list_workdir_registry(&api.config.workspace_id, 500)?;
|
||||||
|
workers
|
||||||
|
.into_iter()
|
||||||
|
.map(|worker| {
|
||||||
|
let record = sync_worker_observation(api, &worker)?;
|
||||||
|
let links = api
|
||||||
|
.store
|
||||||
|
.list_worker_workdir_links(&api.config.workspace_id, &record.worker)?;
|
||||||
|
let summary =
|
||||||
|
merge_worker_registry_projection(Some(&worker), &record, links, &workdirs);
|
||||||
|
project_workspace_worker(api, summary)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn workers_response(
|
||||||
|
api: WorkspaceApi,
|
||||||
|
) -> ApiResult<workspace_api::ListResponse<workspace_api::WorkerSummary>> {
|
||||||
let limit = api.config.max_records.min(200);
|
let limit = api.config.max_records.min(200);
|
||||||
let runtime_workers = api.runtime.list_workers(limit);
|
let runtime_workers = api.runtime.list_workers(limit);
|
||||||
let mut observed = std::collections::BTreeMap::new();
|
let mut observed = std::collections::BTreeMap::new();
|
||||||
@@ -11196,34 +11223,20 @@ fn workers_response(api: WorkspaceApi) -> ApiResult<RuntimeListResponse<WorkerSu
|
|||||||
let links = api
|
let links = api
|
||||||
.store
|
.store
|
||||||
.list_worker_workdir_links(&api.config.workspace_id, &record.worker)?;
|
.list_worker_workdir_links(&api.config.workspace_id, &record.worker)?;
|
||||||
let mut summary = merge_worker_registry_projection(
|
let summary = merge_worker_registry_projection(
|
||||||
observed.get(&record.worker),
|
observed.get(&record.worker),
|
||||||
&record,
|
&record,
|
||||||
links,
|
links,
|
||||||
&workdir_records,
|
&workdir_records,
|
||||||
);
|
);
|
||||||
summary.resource_key = Some(
|
items.push(project_workspace_worker(&api, summary)?);
|
||||||
api.store
|
|
||||||
.resource_key(
|
|
||||||
&api.config.workspace_id,
|
|
||||||
WorkspaceResourceKind::Worker,
|
|
||||||
&record.worker.worker_id,
|
|
||||||
)?
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::Store(format!(
|
|
||||||
"Workspace Worker `{}` has no resource key",
|
|
||||||
record.worker.worker_id
|
|
||||||
))
|
|
||||||
})?,
|
|
||||||
);
|
|
||||||
items.push(summary);
|
|
||||||
}
|
}
|
||||||
Ok(RuntimeListResponse {
|
Ok(workspace_api::ListResponse {
|
||||||
workspace_id: api.config.workspace_id,
|
workspace_id: api.config.workspace_id,
|
||||||
limit,
|
limit,
|
||||||
items,
|
items,
|
||||||
source: "backend_worker_registry".to_string(),
|
source: "backend_worker_registry".to_string(),
|
||||||
diagnostics,
|
diagnostics: diagnostics.into_iter().map(Into::into).collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12084,7 +12097,6 @@ fn record_worker_summary(
|
|||||||
fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary {
|
fn worker_summary_from_registry(record: &WorkerRegistryRecord) -> WorkerSummary {
|
||||||
WorkerSummary {
|
WorkerSummary {
|
||||||
worker: record.worker.clone(),
|
worker: record.worker.clone(),
|
||||||
resource_key: None,
|
|
||||||
host_id: "backend-registry".to_string(),
|
host_id: "backend-registry".to_string(),
|
||||||
display_name: record.display_name.clone(),
|
display_name: record.display_name.clone(),
|
||||||
label: record.display_name.clone(),
|
label: record.display_name.clone(),
|
||||||
@@ -15979,11 +15991,11 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(retried_restore.worker_id, first_worker.worker.worker_id);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
retried_restore.worker_ref.worker_id,
|
retried_restore.result.state,
|
||||||
first_worker.worker.worker_id
|
workspace_api::WorkerOperationState::Accepted
|
||||||
);
|
);
|
||||||
assert_eq!(retried_restore.result.state, WorkerOperationState::Accepted);
|
|
||||||
let restored_assignment = api
|
let restored_assignment = api
|
||||||
.store
|
.store
|
||||||
.get_current_ticket_worker_assignment(TEST_WORKSPACE_ID, &second_ticket.id)
|
.get_current_ticket_worker_assignment(TEST_WORKSPACE_ID, &second_ticket.id)
|
||||||
@@ -18159,6 +18171,23 @@ mod tests {
|
|||||||
assert_eq!(worker["profile"], "builtin:companion");
|
assert_eq!(worker["profile"], "builtin:companion");
|
||||||
assert!(worker.get("role").is_none());
|
assert!(worker.get("role").is_none());
|
||||||
assert_eq!(worker["worker_id"], created["worker_id"]);
|
assert_eq!(worker["worker_id"], created["worker_id"]);
|
||||||
|
let resource_key = worker["resource_key"]
|
||||||
|
.as_str()
|
||||||
|
.expect("Workspace Worker list must project a resource key");
|
||||||
|
assert!(resource_key.starts_with("W-"));
|
||||||
|
|
||||||
|
let runtime_workers =
|
||||||
|
get_json(app.clone(), "/api/runtimes/embedded-worker-runtime/workers").await;
|
||||||
|
let runtime_workers = serde_json::from_value::<
|
||||||
|
workspace_api::ListResponse<workspace_api::WorkerSummary>,
|
||||||
|
>(runtime_workers)
|
||||||
|
.expect("Runtime-scoped Worker list must use the shared Workspace API contract");
|
||||||
|
assert!(
|
||||||
|
runtime_workers
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.any(|worker| worker.resource_key == resource_key)
|
||||||
|
);
|
||||||
let detail_path = format!(
|
let detail_path = format!(
|
||||||
"/api/runtimes/{}/workers/{}",
|
"/api/runtimes/{}/workers/{}",
|
||||||
created["runtime_id"].as_str().unwrap(),
|
created["runtime_id"].as_str().unwrap(),
|
||||||
|
|||||||
Reference in New Issue
Block a user