runtime: own working directories
This commit is contained in:
parent
afc43780a7
commit
fa233ba025
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::catalog::{CreateWorkerRequest, WorkingDirectoryStatus};
|
use crate::catalog::{CreateWorkerRequest, WorkingDirectoryRequest, WorkingDirectoryStatus};
|
||||||
use crate::config_bundle::ConfigBundle;
|
use crate::config_bundle::ConfigBundle;
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
use crate::identity::WorkerRef;
|
use crate::identity::WorkerRef;
|
||||||
use crate::interaction::WorkerInput;
|
use crate::interaction::WorkerInput;
|
||||||
#[cfg(feature = "ws-server")]
|
#[cfg(feature = "ws-server")]
|
||||||
use crate::observation::WorkerObservationEvent;
|
use crate::observation::WorkerObservationEvent;
|
||||||
use crate::working_directory::WorkingDirectoryBinding;
|
use crate::working_directory::{WorkingDirectoryBinding, WorkingDirectoryDiagnostic};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -313,6 +313,42 @@ pub trait WorkerExecutionBackend: Send + Sync + 'static {
|
||||||
|
|
||||||
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult;
|
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult;
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
_request: &WorkingDirectoryRequest,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_unsupported",
|
||||||
|
"Worker execution backend does not support working directory materialization",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> Vec<WorkingDirectoryStatus> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_unknown",
|
||||||
|
format!("working directory `{working_directory_id}` is not known to this Runtime"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_cleanup_unsupported",
|
||||||
|
format!(
|
||||||
|
"working directory `{working_directory_id}` cannot be cleaned up by this backend"
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn dispatch_input(
|
fn dispatch_input(
|
||||||
&self,
|
&self,
|
||||||
handle: &WorkerExecutionHandle,
|
handle: &WorkerExecutionHandle,
|
||||||
|
|
@ -358,6 +394,31 @@ impl WorkerExecutionBackendRef {
|
||||||
self.backend.spawn_worker(request)
|
self.backend.spawn_worker(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: &WorkingDirectoryRequest,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
self.backend.create_working_directory(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn list_working_directories(&self) -> Vec<WorkingDirectoryStatus> {
|
||||||
|
self.backend.list_working_directories()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
self.backend.working_directory(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
self.backend.cleanup_working_directory(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn dispatch_input(
|
pub(crate) fn dispatch_input(
|
||||||
&self,
|
&self,
|
||||||
handle: &WorkerExecutionHandle,
|
handle: &WorkerExecutionHandle,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
use crate::Runtime;
|
use crate::Runtime;
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, WorkerDetail, WorkerLifecycleAck, WorkerSummary,
|
ConfigBundleRef, CreateWorkerRequest, WorkerDetail, WorkerLifecycleAck, WorkerSummary,
|
||||||
|
WorkingDirectoryRequest, WorkingDirectoryStatus,
|
||||||
};
|
};
|
||||||
use crate::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
|
use crate::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
|
||||||
use crate::error::RuntimeError;
|
use crate::error::RuntimeError;
|
||||||
|
|
@ -138,6 +139,14 @@ pub fn runtime_http_router(runtime: Runtime, local_token: Option<String>) -> Rou
|
||||||
"/v1/config-bundles/{bundle_id}/availability",
|
"/v1/config-bundles/{bundle_id}/availability",
|
||||||
get(check_config_bundle),
|
get(check_config_bundle),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/v1/working-directories",
|
||||||
|
get(list_working_directories).post(create_working_directory),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/v1/working-directories/{working_directory_id}",
|
||||||
|
get(get_working_directory).delete(cleanup_working_directory),
|
||||||
|
)
|
||||||
.route("/v1/workers", get(list_workers).post(create_worker))
|
.route("/v1/workers", get(list_workers).post(create_worker))
|
||||||
.route("/v1/workers/{worker_id}", get(get_worker))
|
.route("/v1/workers/{worker_id}", get(get_worker))
|
||||||
.route("/v1/workers/{worker_id}/input", post(send_worker_input))
|
.route("/v1/workers/{worker_id}/input", post(send_worker_input))
|
||||||
|
|
@ -197,6 +206,18 @@ pub struct RuntimeHttpWorkersResponse {
|
||||||
pub workers: Vec<WorkerSummary>,
|
pub workers: Vec<WorkerSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `GET /v1/working-directories` response.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeHttpWorkingDirectoriesResponse {
|
||||||
|
pub working_directories: Vec<WorkingDirectoryStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Working directory response used by create/detail/delete endpoints.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeHttpWorkingDirectoryResponse {
|
||||||
|
pub working_directory: WorkingDirectoryStatus,
|
||||||
|
}
|
||||||
|
|
||||||
/// Worker detail response used by create/detail endpoints.
|
/// Worker detail response used by create/detail endpoints.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RuntimeHttpWorkerResponse {
|
pub struct RuntimeHttpWorkerResponse {
|
||||||
|
|
@ -399,6 +420,58 @@ async fn list_workers(
|
||||||
Ok(Json(RuntimeHttpWorkersResponse { workers }))
|
Ok(Json(RuntimeHttpWorkersResponse { workers }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn list_working_directories(
|
||||||
|
State(state): State<RuntimeHttpState>,
|
||||||
|
) -> RestResult<RuntimeHttpWorkingDirectoriesResponse> {
|
||||||
|
let working_directories = state
|
||||||
|
.runtime
|
||||||
|
.list_working_directories()
|
||||||
|
.map_err(RuntimeHttpRestError::runtime)?;
|
||||||
|
Ok(Json(RuntimeHttpWorkingDirectoriesResponse {
|
||||||
|
working_directories,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_working_directory(
|
||||||
|
State(state): State<RuntimeHttpState>,
|
||||||
|
body: Result<Json<WorkingDirectoryRequest>, JsonRejection>,
|
||||||
|
) -> RestResult<RuntimeHttpWorkingDirectoryResponse> {
|
||||||
|
let Json(request) = body.map_err(RuntimeHttpRestError::json_rejection)?;
|
||||||
|
let working_directory = state
|
||||||
|
.runtime
|
||||||
|
.create_working_directory(request)
|
||||||
|
.map_err(RuntimeHttpRestError::runtime)?;
|
||||||
|
Ok(Json(RuntimeHttpWorkingDirectoryResponse {
|
||||||
|
working_directory,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_working_directory(
|
||||||
|
State(state): State<RuntimeHttpState>,
|
||||||
|
Path(working_directory_id): Path<String>,
|
||||||
|
) -> RestResult<RuntimeHttpWorkingDirectoryResponse> {
|
||||||
|
let working_directory = state
|
||||||
|
.runtime
|
||||||
|
.working_directory(&working_directory_id)
|
||||||
|
.map_err(RuntimeHttpRestError::runtime)?;
|
||||||
|
Ok(Json(RuntimeHttpWorkingDirectoryResponse {
|
||||||
|
working_directory,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cleanup_working_directory(
|
||||||
|
State(state): State<RuntimeHttpState>,
|
||||||
|
Path(working_directory_id): Path<String>,
|
||||||
|
) -> RestResult<RuntimeHttpWorkingDirectoryResponse> {
|
||||||
|
let working_directory = state
|
||||||
|
.runtime
|
||||||
|
.cleanup_working_directory(&working_directory_id)
|
||||||
|
.map_err(RuntimeHttpRestError::runtime)?;
|
||||||
|
Ok(Json(RuntimeHttpWorkingDirectoryResponse {
|
||||||
|
working_directory,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_worker(
|
async fn get_worker(
|
||||||
State(state): State<RuntimeHttpState>,
|
State(state): State<RuntimeHttpState>,
|
||||||
Path(worker_id): Path<String>,
|
Path(worker_id): Path<String>,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::catalog::{
|
use crate::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail, WorkerLifecycleAck,
|
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail, WorkerLifecycleAck,
|
||||||
WorkerStatus, WorkerSummary, WorkingDirectoryStatus as CatalogWorkingDirectoryStatus,
|
WorkerStatus, WorkerSummary, WorkingDirectoryRequest,
|
||||||
|
WorkingDirectoryStatus as CatalogWorkingDirectoryStatus,
|
||||||
};
|
};
|
||||||
use crate::config_bundle::{
|
use crate::config_bundle::{
|
||||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary, validate_config_bundle,
|
ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary, validate_config_bundle,
|
||||||
|
|
@ -234,6 +235,79 @@ impl Runtime {
|
||||||
Ok(event_id)
|
Ok(event_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a Runtime-owned working directory through the attached execution backend.
|
||||||
|
pub fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: WorkingDirectoryRequest,
|
||||||
|
) -> Result<CatalogWorkingDirectoryStatus, RuntimeError> {
|
||||||
|
let backend = {
|
||||||
|
let state = self.lock()?;
|
||||||
|
state.ensure_running()?;
|
||||||
|
state.execution_backend.clone().ok_or_else(|| {
|
||||||
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
|
message: "working directory creation requires an execution backend".to_string(),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
backend
|
||||||
|
.create_working_directory(&request)
|
||||||
|
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List Runtime-owned working directories through the attached execution backend.
|
||||||
|
pub fn list_working_directories(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<CatalogWorkingDirectoryStatus>, RuntimeError> {
|
||||||
|
let backend = {
|
||||||
|
let state = self.lock()?;
|
||||||
|
state.ensure_running()?;
|
||||||
|
state.execution_backend.clone().ok_or_else(|| {
|
||||||
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
|
message: "working directory listing requires an execution backend".to_string(),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
Ok(backend.list_working_directories())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a Runtime-owned working directory status.
|
||||||
|
pub fn working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<CatalogWorkingDirectoryStatus, RuntimeError> {
|
||||||
|
let backend = {
|
||||||
|
let state = self.lock()?;
|
||||||
|
state.ensure_running()?;
|
||||||
|
state.execution_backend.clone().ok_or_else(|| {
|
||||||
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
|
message: "working directory lookup requires an execution backend".to_string(),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
backend
|
||||||
|
.working_directory(working_directory_id)
|
||||||
|
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleanup a Runtime-owned working directory.
|
||||||
|
pub fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<CatalogWorkingDirectoryStatus, RuntimeError> {
|
||||||
|
let backend = {
|
||||||
|
let state = self.lock()?;
|
||||||
|
state.ensure_running()?;
|
||||||
|
state.execution_backend.clone().ok_or_else(|| {
|
||||||
|
RuntimeError::ExecutionBackendUnavailable {
|
||||||
|
message: "working directory cleanup requires an execution backend".to_string(),
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
backend
|
||||||
|
.cleanup_working_directory(working_directory_id)
|
||||||
|
.map_err(|diagnostic| RuntimeError::InvalidRequest(diagnostic.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a Worker through the canonical ConfigBundle + execution backend path.
|
/// Create a Worker through the canonical ConfigBundle + execution backend path.
|
||||||
pub fn create_worker(
|
pub fn create_worker(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, mpsc};
|
use std::sync::{Arc, Mutex, mpsc};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::catalog::{WorkingDirectoryRequest, WorkingDirectoryStatus};
|
||||||
use crate::config_bundle::verified_profile_source_archive;
|
use crate::config_bundle::verified_profile_source_archive;
|
||||||
use crate::execution::{
|
use crate::execution::{
|
||||||
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult,
|
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult,
|
||||||
|
|
@ -24,7 +25,9 @@ use crate::resource::{
|
||||||
BackendResourceClient, BackendResourceError, ProfileSourceArchiveCache,
|
BackendResourceClient, BackendResourceError, ProfileSourceArchiveCache,
|
||||||
build_profile_source_archive_fetch_request, profile_source_archive_from_response,
|
build_profile_source_archive_fetch_request, profile_source_archive_from_response,
|
||||||
};
|
};
|
||||||
use crate::working_directory::{WorkingDirectoryBinding, WorkingDirectoryMaterializer};
|
use crate::working_directory::{
|
||||||
|
WorkingDirectoryBinding, WorkingDirectoryDiagnostic, WorkingDirectoryMaterializer,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use manifest::paths;
|
use manifest::paths;
|
||||||
use protocol::{Method, Segment, WorkerStatus};
|
use protocol::{Method, Segment, WorkerStatus};
|
||||||
|
|
@ -441,6 +444,52 @@ where
|
||||||
&self.backend_id
|
&self.backend_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: &WorkingDirectoryRequest,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
|
return Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_materializer_unavailable",
|
||||||
|
"working directory materialization requested, but no materializer is configured for this runtime backend",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(materializer.create(request)?.status())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> Vec<WorkingDirectoryStatus> {
|
||||||
|
self.working_directory_materializer
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|materializer| materializer.list_working_directories().ok())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
|
return Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_materializer_unavailable",
|
||||||
|
"working directory lookup requested, but no materializer is configured for this runtime backend",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
materializer.working_directory_status(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
|
return Err(WorkingDirectoryDiagnostic::rejected(
|
||||||
|
"working_directory_materializer_unavailable",
|
||||||
|
"working directory cleanup requested, but no materializer is configured for this runtime backend",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
materializer.cleanup_working_directory(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult {
|
fn spawn_worker(&self, request: WorkerExecutionSpawnRequest) -> WorkerExecutionSpawnResult {
|
||||||
if self
|
if self
|
||||||
.workers
|
.workers
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,10 @@ pub struct WorkingDirectoryDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkingDirectoryDiagnostic {
|
impl WorkingDirectoryDiagnostic {
|
||||||
|
pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||||
|
Self::new(code, message)
|
||||||
|
}
|
||||||
|
|
||||||
fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
code: code.into(),
|
code: code.into(),
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use std::{
|
||||||
use worker_runtime::catalog::{
|
use worker_runtime::catalog::{
|
||||||
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail,
|
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail,
|
||||||
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
|
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
|
||||||
WorkingDirectorySummary,
|
WorkingDirectoryStatus, WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use worker_runtime::config_bundle::{
|
use worker_runtime::config_bundle::{
|
||||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
|
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
|
||||||
|
|
@ -32,6 +32,7 @@ use worker_runtime::http_server::{
|
||||||
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpTranscriptResponse,
|
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpTranscriptResponse,
|
||||||
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
|
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
|
||||||
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
|
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
|
||||||
|
RuntimeHttpWorkingDirectoriesResponse, RuntimeHttpWorkingDirectoryResponse,
|
||||||
};
|
};
|
||||||
use worker_runtime::identity::{
|
use worker_runtime::identity::{
|
||||||
RuntimeId as EmbeddedRuntimeId, WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef,
|
RuntimeId as EmbeddedRuntimeId, WorkerId as EmbeddedWorkerId, WorkerRef as EmbeddedWorkerRef,
|
||||||
|
|
@ -266,6 +267,14 @@ pub struct WorkerLookupResult {
|
||||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct RuntimeWorkingDirectoryResult {
|
||||||
|
pub state: WorkerOperationState,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub working_directory: Option<WorkingDirectoryStatus>,
|
||||||
|
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Browser-safe worker spawn request shape.
|
/// Browser-safe worker spawn request shape.
|
||||||
///
|
///
|
||||||
/// The request carries Browser-facing launch semantics only: workspace intent,
|
/// The request carries Browser-facing launch semantics only: workspace intent,
|
||||||
|
|
@ -537,6 +546,56 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
|
||||||
|
|
||||||
fn worker(&self, worker_id: &str) -> WorkerLookupResult;
|
fn worker(&self, worker_id: &str) -> WorkerLookupResult;
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
_request: WorkingDirectoryRequest,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Unsupported,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic(
|
||||||
|
"runtime_working_directory_create_unsupported",
|
||||||
|
DiagnosticSeverity::Info,
|
||||||
|
"runtime does not implement working directory creation".to_string(),
|
||||||
|
)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> RuntimeList<WorkingDirectoryStatus> {
|
||||||
|
RuntimeList::new(Vec::new(), Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(&self, working_directory_id: &str) -> RuntimeWorkingDirectoryResult {
|
||||||
|
RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Unsupported,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic(
|
||||||
|
"runtime_working_directory_lookup_unsupported",
|
||||||
|
DiagnosticSeverity::Info,
|
||||||
|
format!(
|
||||||
|
"runtime does not implement working directory lookup for `{working_directory_id}`"
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Unsupported,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic(
|
||||||
|
"runtime_working_directory_cleanup_unsupported",
|
||||||
|
DiagnosticSeverity::Info,
|
||||||
|
format!(
|
||||||
|
"runtime does not implement working directory cleanup for `{working_directory_id}`"
|
||||||
|
),
|
||||||
|
)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
||||||
WorkerSpawnResult {
|
WorkerSpawnResult {
|
||||||
state: WorkerOperationState::Unsupported,
|
state: WorkerOperationState::Unsupported,
|
||||||
|
|
@ -882,6 +941,47 @@ impl RuntimeRegistry {
|
||||||
Ok(runtime.spawn_worker(request))
|
Ok(runtime.spawn_worker(request))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
runtime_id: &str,
|
||||||
|
request: WorkingDirectoryRequest,
|
||||||
|
) -> Result<RuntimeWorkingDirectoryResult, RuntimeRegistryError> {
|
||||||
|
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||||
|
let runtime = self.runtime(runtime_id)?;
|
||||||
|
Ok(runtime.create_working_directory(request))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_working_directories(
|
||||||
|
&self,
|
||||||
|
runtime_id: &str,
|
||||||
|
) -> Result<RuntimeList<WorkingDirectoryStatus>, RuntimeRegistryError> {
|
||||||
|
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||||
|
let runtime = self.runtime(runtime_id)?;
|
||||||
|
Ok(runtime.list_working_directories())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn working_directory(
|
||||||
|
&self,
|
||||||
|
runtime_id: &str,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<RuntimeWorkingDirectoryResult, RuntimeRegistryError> {
|
||||||
|
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||||
|
validate_backend_identifier("working_directory_id", working_directory_id)?;
|
||||||
|
let runtime = self.runtime(runtime_id)?;
|
||||||
|
Ok(runtime.working_directory(working_directory_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
runtime_id: &str,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> Result<RuntimeWorkingDirectoryResult, RuntimeRegistryError> {
|
||||||
|
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||||
|
validate_backend_identifier("working_directory_id", working_directory_id)?;
|
||||||
|
let runtime = self.runtime(runtime_id)?;
|
||||||
|
Ok(runtime.cleanup_working_directory(working_directory_id))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sync_config_bundle(
|
pub fn sync_config_bundle(
|
||||||
&self,
|
&self,
|
||||||
runtime_id: &str,
|
runtime_id: &str,
|
||||||
|
|
@ -1311,6 +1411,64 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: WorkingDirectoryRequest,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.runtime.create_working_directory(request) {
|
||||||
|
Ok(working_directory) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(err) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![embedded_runtime_diagnostic(&err)],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> RuntimeList<WorkingDirectoryStatus> {
|
||||||
|
match self.runtime.list_working_directories() {
|
||||||
|
Ok(items) => RuntimeList::new(items, Vec::new()),
|
||||||
|
Err(err) => RuntimeList::new(Vec::new(), vec![embedded_runtime_diagnostic(&err)]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(&self, working_directory_id: &str) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.runtime.working_directory(working_directory_id) {
|
||||||
|
Ok(working_directory) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(err) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![embedded_runtime_diagnostic(&err)],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.runtime.cleanup_working_directory(working_directory_id) {
|
||||||
|
Ok(working_directory) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(err) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![embedded_runtime_diagnostic(&err)],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
||||||
let mut diagnostics = Vec::new();
|
let mut diagnostics = Vec::new();
|
||||||
if matches!(
|
if matches!(
|
||||||
|
|
@ -1811,6 +1969,13 @@ impl RemoteWorkerRuntime {
|
||||||
self.send_json(self.http.post(self.endpoint(path)).json(body))
|
self.send_json(self.http.post(self.endpoint(path)).json(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_json<T>(&self, path: &str) -> Result<T, RuntimeDiagnostic>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned + Send + 'static,
|
||||||
|
{
|
||||||
|
self.send_json(self.http.delete(self.endpoint(path)))
|
||||||
|
}
|
||||||
|
|
||||||
fn send_json<T>(&self, request: RequestBuilder) -> Result<T, RuntimeDiagnostic>
|
fn send_json<T>(&self, request: RequestBuilder) -> Result<T, RuntimeDiagnostic>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + Send + 'static,
|
T: DeserializeOwned + Send + 'static,
|
||||||
|
|
@ -2035,6 +2200,71 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: WorkingDirectoryRequest,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.post_json::<_, RuntimeHttpWorkingDirectoryResponse>(
|
||||||
|
"/v1/working-directories",
|
||||||
|
&request,
|
||||||
|
) {
|
||||||
|
Ok(response) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(response.working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(diagnostic) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> RuntimeList<WorkingDirectoryStatus> {
|
||||||
|
match self.get_json::<RuntimeHttpWorkingDirectoriesResponse>("/v1/working-directories") {
|
||||||
|
Ok(response) => RuntimeList::new(response.working_directories, Vec::new()),
|
||||||
|
Err(diagnostic) => RuntimeList::new(Vec::new(), vec![diagnostic]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(&self, working_directory_id: &str) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.get_json::<RuntimeHttpWorkingDirectoryResponse>(&format!(
|
||||||
|
"/v1/working-directories/{working_directory_id}"
|
||||||
|
)) {
|
||||||
|
Ok(response) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(response.working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(diagnostic) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> RuntimeWorkingDirectoryResult {
|
||||||
|
match self.delete_json::<RuntimeHttpWorkingDirectoryResponse>(&format!(
|
||||||
|
"/v1/working-directories/{working_directory_id}"
|
||||||
|
)) {
|
||||||
|
Ok(response) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Accepted,
|
||||||
|
working_directory: Some(response.working_directory),
|
||||||
|
diagnostics: Vec::new(),
|
||||||
|
},
|
||||||
|
Err(diagnostic) => RuntimeWorkingDirectoryResult {
|
||||||
|
state: WorkerOperationState::Rejected,
|
||||||
|
working_directory: None,
|
||||||
|
diagnostics: vec![diagnostic],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
fn spawn_worker(&self, request: WorkerSpawnRequest) -> WorkerSpawnResult {
|
||||||
if matches!(
|
if matches!(
|
||||||
request.acceptance,
|
request.acceptance,
|
||||||
|
|
@ -2801,6 +3031,14 @@ fn remote_reqwest_diagnostic(runtime_id: &str, err: reqwest::Error) -> RuntimeDi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sanitize_remote_runtime_message(code: &str, message: &str) -> String {
|
||||||
|
if message.contains('/') || message.contains('\\') {
|
||||||
|
format!("remote Runtime returned {code}; backend-private details were omitted")
|
||||||
|
} else {
|
||||||
|
message.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn remote_http_status_diagnostic(
|
fn remote_http_status_diagnostic(
|
||||||
runtime_id: &str,
|
runtime_id: &str,
|
||||||
status: StatusCode,
|
status: StatusCode,
|
||||||
|
|
@ -2811,6 +3049,10 @@ fn remote_http_status_diagnostic(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|error| error.error.code.as_str())
|
.map(|error| error.error.code.as_str())
|
||||||
.unwrap_or("remote_http_error");
|
.unwrap_or("remote_http_error");
|
||||||
|
let remote_message = error
|
||||||
|
.as_ref()
|
||||||
|
.map(|error| sanitize_remote_runtime_message(remote_code, &error.error.message))
|
||||||
|
.unwrap_or_else(|| "remote Runtime did not return a typed error body".to_string());
|
||||||
let (code, severity) = match status {
|
let (code, severity) = match status {
|
||||||
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
|
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN => {
|
||||||
("remote_runtime_auth_failed", DiagnosticSeverity::Error)
|
("remote_runtime_auth_failed", DiagnosticSeverity::Error)
|
||||||
|
|
@ -2820,14 +3062,12 @@ fn remote_http_status_diagnostic(
|
||||||
("remote_runtime_unsupported", DiagnosticSeverity::Warning)
|
("remote_runtime_unsupported", DiagnosticSeverity::Warning)
|
||||||
}
|
}
|
||||||
_ if status.is_server_error() => ("remote_runtime_http_error", DiagnosticSeverity::Error),
|
_ if status.is_server_error() => ("remote_runtime_http_error", DiagnosticSeverity::Error),
|
||||||
_ => ("remote_runtime_http_rejected", DiagnosticSeverity::Warning),
|
_ => (remote_code, DiagnosticSeverity::Warning),
|
||||||
};
|
};
|
||||||
diagnostic(
|
diagnostic(
|
||||||
code,
|
code,
|
||||||
severity,
|
severity,
|
||||||
format!(
|
format!("Remote Runtime '{runtime_id}' rejected request (HTTP {status}): {remote_message}"),
|
||||||
"Remote Runtime '{runtime_id}' rejected request ({remote_code}, HTTP {status}); internal details were sanitized"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use worker_runtime::resource::{BackendResourceError, BackendResourceFetchRequest};
|
use worker_runtime::resource::{BackendResourceError, BackendResourceFetchRequest};
|
||||||
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
||||||
use worker_runtime::working_directory::{
|
use worker_runtime::working_directory::LocalGitWorktreeMaterializer;
|
||||||
LocalGitWorktreeMaterializer, WorkingDirectoryDiagnostic, WorkingDirectoryMaterializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::companion::{
|
use crate::companion::{
|
||||||
CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse,
|
CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse,
|
||||||
|
|
@ -67,6 +65,10 @@ use worker_runtime::interaction::{
|
||||||
|
|
||||||
const EMBEDDED_WORKER_RUNTIME_ID: &str = "embedded-worker-runtime";
|
const EMBEDDED_WORKER_RUNTIME_ID: &str = "embedded-worker-runtime";
|
||||||
|
|
||||||
|
fn embedded_runtime_id() -> String {
|
||||||
|
EMBEDDED_WORKER_RUNTIME_ID.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub enum AuthConfig {
|
pub enum AuthConfig {
|
||||||
/// Local/dev-only mode. If a token is configured by a future entrypoint, it
|
/// Local/dev-only mode. If a token is configured by a future entrypoint, it
|
||||||
|
|
@ -162,14 +164,10 @@ pub struct WorkspaceApi {
|
||||||
companion: Arc<CompanionConsole>,
|
companion: Arc<CompanionConsole>,
|
||||||
observation_proxy: BackendObservationProxy,
|
observation_proxy: BackendObservationProxy,
|
||||||
resource_broker: BackendResourceBroker,
|
resource_broker: BackendResourceBroker,
|
||||||
working_directory_materializer: Arc<LocalGitWorktreeMaterializer>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkspaceApi {
|
impl WorkspaceApi {
|
||||||
pub async fn new(config: ServerConfig, store: Arc<dyn ControlPlaneStore>) -> Result<Self> {
|
pub async fn new(config: ServerConfig, store: Arc<dyn ControlPlaneStore>) -> Result<Self> {
|
||||||
let materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
|
||||||
config.embedded_runtime_store_root.clone(),
|
|
||||||
));
|
|
||||||
let resource_broker = BackendResourceBroker::default();
|
let resource_broker = BackendResourceBroker::default();
|
||||||
let execution_backend = WorkerRuntimeExecutionBackend::new(
|
let execution_backend = WorkerRuntimeExecutionBackend::new(
|
||||||
ProfileRuntimeWorkerFactory::new(config.workspace_root.clone())
|
ProfileRuntimeWorkerFactory::new(config.workspace_root.clone())
|
||||||
|
|
@ -180,7 +178,9 @@ impl WorkspaceApi {
|
||||||
"failed to initialize embedded Worker backend: {err}"
|
"failed to initialize embedded Worker backend: {err}"
|
||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
.with_working_directory_materializer((*materializer).clone());
|
.with_working_directory_materializer(LocalGitWorktreeMaterializer::new(
|
||||||
|
config.embedded_runtime_store_root.clone(),
|
||||||
|
));
|
||||||
Self::new_with_execution_backend_and_broker(
|
Self::new_with_execution_backend_and_broker(
|
||||||
config,
|
config,
|
||||||
store,
|
store,
|
||||||
|
|
@ -241,9 +241,6 @@ impl WorkspaceApi {
|
||||||
let runtime = Arc::new(runtime);
|
let runtime = Arc::new(runtime);
|
||||||
let companion = Arc::new(CompanionConsole::new(runtime.clone()));
|
let companion = Arc::new(CompanionConsole::new(runtime.clone()));
|
||||||
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
|
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
|
||||||
let working_directory_materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
|
||||||
config.embedded_runtime_store_root.clone(),
|
|
||||||
));
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
records: LocalProjectRecordReader::new(config.workspace_root.clone()),
|
records: LocalProjectRecordReader::new(config.workspace_root.clone()),
|
||||||
config,
|
config,
|
||||||
|
|
@ -252,7 +249,6 @@ impl WorkspaceApi {
|
||||||
companion,
|
companion,
|
||||||
observation_proxy,
|
observation_proxy,
|
||||||
resource_broker,
|
resource_broker,
|
||||||
working_directory_materializer,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,6 +322,14 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||||
)
|
)
|
||||||
.route("/api/hosts", get(list_hosts))
|
.route("/api/hosts", get(list_hosts))
|
||||||
.route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts))
|
.route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts))
|
||||||
|
.route(
|
||||||
|
"/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories",
|
||||||
|
get(scoped_list_runtime_working_directories).post(scoped_create_runtime_working_directory),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/w/{workspace_id}/runtimes/{runtime_id}/working-directories/{working_directory_id}",
|
||||||
|
get(scoped_runtime_working_directory_detail).delete(scoped_cleanup_runtime_working_directory),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/w/{workspace_id}/working-directories",
|
"/api/w/{workspace_id}/working-directories",
|
||||||
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
||||||
|
|
@ -662,6 +666,8 @@ pub enum BrowserWorkingDirectoryCleanupPolicy {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BrowserWorkingDirectoryCreateRequest {
|
pub struct BrowserWorkingDirectoryCreateRequest {
|
||||||
|
#[serde(default = "embedded_runtime_id")]
|
||||||
|
pub runtime_id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selector: Option<String>,
|
pub selector: Option<String>,
|
||||||
|
|
@ -807,6 +813,13 @@ struct ScopedWorkingDirectoryPath {
|
||||||
working_directory_id: String,
|
working_directory_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ScopedRuntimeWorkingDirectoryPath {
|
||||||
|
workspace_id: String,
|
||||||
|
runtime_id: String,
|
||||||
|
working_directory_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ScopedConfigBundlePath {
|
struct ScopedConfigBundlePath {
|
||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
|
|
@ -1054,6 +1067,52 @@ async fn scoped_get_worker_launch_options(
|
||||||
get_worker_launch_options(State(api)).await
|
get_worker_launch_options(State(api)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn scoped_list_runtime_working_directories(
|
||||||
|
State(api): State<WorkspaceApi>,
|
||||||
|
AxumPath(path): AxumPath<ScopedRuntimePath>,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryListResponse>> {
|
||||||
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
|
let list = api
|
||||||
|
.runtime
|
||||||
|
.list_working_directories(&path.runtime_id)
|
||||||
|
.map_err(|err| err.into_error())?;
|
||||||
|
Ok(Json(BrowserWorkingDirectoryListResponse {
|
||||||
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
items: list
|
||||||
|
.items
|
||||||
|
.into_iter()
|
||||||
|
.map(|status| status.summary)
|
||||||
|
.collect(),
|
||||||
|
diagnostics: list.diagnostics,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scoped_create_runtime_working_directory(
|
||||||
|
State(api): State<WorkspaceApi>,
|
||||||
|
AxumPath(path): AxumPath<ScopedRuntimePath>,
|
||||||
|
Json(mut request): Json<BrowserWorkingDirectoryCreateRequest>,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
|
request.runtime_id = path.runtime_id;
|
||||||
|
create_working_directory_for_runtime(api, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scoped_runtime_working_directory_detail(
|
||||||
|
State(api): State<WorkspaceApi>,
|
||||||
|
AxumPath(path): AxumPath<ScopedRuntimeWorkingDirectoryPath>,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
|
working_directory_detail_for_runtime(api, &path.runtime_id, &path.working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scoped_cleanup_runtime_working_directory(
|
||||||
|
State(api): State<WorkspaceApi>,
|
||||||
|
AxumPath(path): AxumPath<ScopedRuntimeWorkingDirectoryPath>,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
|
cleanup_working_directory_for_runtime(api, &path.runtime_id, &path.working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
async fn scoped_list_working_directories(
|
async fn scoped_list_working_directories(
|
||||||
State(api): State<WorkspaceApi>,
|
State(api): State<WorkspaceApi>,
|
||||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||||
|
|
@ -1073,22 +1132,7 @@ async fn scoped_create_working_directory(
|
||||||
Json(request): Json<BrowserWorkingDirectoryCreateRequest>,
|
Json(request): Json<BrowserWorkingDirectoryCreateRequest>,
|
||||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let working_directory_request = working_directory_request_for_browser(&api, request)?;
|
create_working_directory_for_runtime(api, request)
|
||||||
let binding = api
|
|
||||||
.working_directory_materializer
|
|
||||||
.create(&working_directory_request)
|
|
||||||
.map_err(|diagnostic| {
|
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
|
||||||
code: diagnostic.code,
|
|
||||||
message: diagnostic.message,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
|
||||||
item: binding.status().summary,
|
|
||||||
diagnostics: Vec::new(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_working_directory_detail(
|
async fn scoped_working_directory_detail(
|
||||||
|
|
@ -1096,21 +1140,11 @@ async fn scoped_working_directory_detail(
|
||||||
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
working_directory_detail_for_runtime(
|
||||||
.working_directory_materializer
|
api,
|
||||||
.working_directory_status(&path.working_directory_id)
|
EMBEDDED_WORKER_RUNTIME_ID,
|
||||||
.map_err(|diagnostic| {
|
&path.working_directory_id,
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
)
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
|
||||||
code: diagnostic.code,
|
|
||||||
message: diagnostic.message,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
|
||||||
item: status.summary,
|
|
||||||
diagnostics: Vec::new(),
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_cleanup_working_directory(
|
async fn scoped_cleanup_working_directory(
|
||||||
|
|
@ -1118,20 +1152,89 @@ async fn scoped_cleanup_working_directory(
|
||||||
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
cleanup_working_directory_for_runtime(
|
||||||
.working_directory_materializer
|
api,
|
||||||
.cleanup_working_directory(&path.working_directory_id)
|
EMBEDDED_WORKER_RUNTIME_ID,
|
||||||
.map_err(|diagnostic| {
|
&path.working_directory_id,
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
)
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
}
|
||||||
code: diagnostic.code,
|
|
||||||
message: diagnostic.message,
|
fn create_working_directory_for_runtime(
|
||||||
})
|
api: WorkspaceApi,
|
||||||
})?;
|
request: BrowserWorkingDirectoryCreateRequest,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
let runtime_id = request.runtime_id.clone();
|
||||||
|
let working_directory_request = working_directory_request_for_browser(&api, request)?;
|
||||||
|
let result = api
|
||||||
|
.runtime
|
||||||
|
.create_working_directory(&runtime_id, working_directory_request)
|
||||||
|
.map_err(|err| err.into_error())?;
|
||||||
|
let Some(working_directory) = result.working_directory else {
|
||||||
|
return Err(ApiError::with_diagnostics(
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
|
runtime_id,
|
||||||
|
code: "workspace_working_directory_create_failed".to_string(),
|
||||||
|
message: "Runtime did not create working directory".to_string(),
|
||||||
|
},
|
||||||
|
result.diagnostics,
|
||||||
|
));
|
||||||
|
};
|
||||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
workspace_id: api.config.workspace_id.clone(),
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
item: status.summary,
|
item: working_directory.summary,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: result.diagnostics,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory_detail_for_runtime(
|
||||||
|
api: WorkspaceApi,
|
||||||
|
runtime_id: &str,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
let result = api
|
||||||
|
.runtime
|
||||||
|
.working_directory(runtime_id, working_directory_id)
|
||||||
|
.map_err(|err| err.into_error())?;
|
||||||
|
let Some(working_directory) = result.working_directory else {
|
||||||
|
return Err(ApiError::with_diagnostics(
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
|
runtime_id: runtime_id.to_string(),
|
||||||
|
code: "workspace_working_directory_lookup_failed".to_string(),
|
||||||
|
message: "Runtime did not return working directory".to_string(),
|
||||||
|
},
|
||||||
|
result.diagnostics,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
item: working_directory.summary,
|
||||||
|
diagnostics: result.diagnostics,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory_for_runtime(
|
||||||
|
api: WorkspaceApi,
|
||||||
|
runtime_id: &str,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||||
|
let result = api
|
||||||
|
.runtime
|
||||||
|
.cleanup_working_directory(runtime_id, working_directory_id)
|
||||||
|
.map_err(|err| err.into_error())?;
|
||||||
|
let Some(working_directory) = result.working_directory else {
|
||||||
|
return Err(ApiError::with_diagnostics(
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
|
runtime_id: runtime_id.to_string(),
|
||||||
|
code: "workspace_working_directory_cleanup_failed".to_string(),
|
||||||
|
message: "Runtime did not cleanup working directory".to_string(),
|
||||||
|
},
|
||||||
|
result.diagnostics,
|
||||||
|
));
|
||||||
|
};
|
||||||
|
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||||
|
workspace_id: api.config.workspace_id.clone(),
|
||||||
|
item: working_directory.summary,
|
||||||
|
diagnostics: result.diagnostics,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1812,26 +1915,7 @@ async fn create_workspace_worker(
|
||||||
working_directory_id: selection.working_directory_id,
|
working_directory_id: selection.working_directory_id,
|
||||||
relative_cwd: selection.relative_cwd,
|
relative_cwd: selection.relative_cwd,
|
||||||
});
|
});
|
||||||
if resolved_working_directory.is_some() && request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID {
|
validate_working_directory_claim_for_browser(resolved_working_directory.as_ref())?;
|
||||||
return Err(Error::RuntimeOperationFailed {
|
|
||||||
runtime_id: request.runtime_id.clone(),
|
|
||||||
code: "working_directory_runtime_unsupported".to_string(),
|
|
||||||
message:
|
|
||||||
"created working directories are only supported by the embedded local runtime in v0"
|
|
||||||
.to_string(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
if let Some(working_directory) = resolved_working_directory.as_ref() {
|
|
||||||
api.working_directory_materializer
|
|
||||||
.bind_working_directory(
|
|
||||||
&working_directory.working_directory_id,
|
|
||||||
working_directory.relative_cwd.as_deref(),
|
|
||||||
)
|
|
||||||
.map_err(|diagnostic| {
|
|
||||||
working_directory_api_error(request.runtime_id.clone(), diagnostic)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
let result = api
|
let result = api
|
||||||
.runtime
|
.runtime
|
||||||
.spawn_worker(
|
.spawn_worker(
|
||||||
|
|
@ -2924,16 +3008,55 @@ fn working_directory_repository_options(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirectorySummary>> {
|
fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirectorySummary>> {
|
||||||
api.working_directory_materializer
|
let list = api
|
||||||
.list_working_directories()
|
.runtime
|
||||||
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
.list_working_directories(EMBEDDED_WORKER_RUNTIME_ID)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|err| err.into_error())?;
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
if !list.diagnostics.is_empty() {
|
||||||
|
return Err(ApiError::with_diagnostics(
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||||
code: diagnostic.code,
|
code: "workspace_working_directory_list_failed".to_string(),
|
||||||
message: diagnostic.message,
|
message: "Runtime did not list working directories".to_string(),
|
||||||
})
|
},
|
||||||
})
|
list.diagnostics,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(list
|
||||||
|
.items
|
||||||
|
.into_iter()
|
||||||
|
.map(|status| status.summary)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_working_directory_claim_for_browser(
|
||||||
|
claim: Option<&WorkingDirectoryClaim>,
|
||||||
|
) -> ApiResult<()> {
|
||||||
|
let Some(claim) = claim else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
if let Some(relative_cwd) = claim.relative_cwd.as_deref() {
|
||||||
|
let path = Path::new(relative_cwd);
|
||||||
|
if path.is_absolute()
|
||||||
|
|| path
|
||||||
|
.components()
|
||||||
|
.any(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
|
||||||
|
{
|
||||||
|
return Err(ApiError::with_diagnostics(
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
|
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||||
|
code: "working_directory_relative_cwd_invalid".to_string(),
|
||||||
|
message: "working directory relative_cwd must stay inside the Runtime-owned working directory".to_string(),
|
||||||
|
},
|
||||||
|
vec![RuntimeDiagnostic {
|
||||||
|
code: "working_directory_relative_cwd_invalid".to_string(),
|
||||||
|
severity: DiagnosticSeverity::Error,
|
||||||
|
message: "relative_cwd must be a relative path without parent traversal".to_string(),
|
||||||
|
}],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn working_directory_request_for_browser(
|
fn working_directory_request_for_browser(
|
||||||
|
|
@ -3065,24 +3188,6 @@ fn worker_create_not_accepted_error(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn working_directory_api_error(
|
|
||||||
runtime_id: String,
|
|
||||||
diagnostic: WorkingDirectoryDiagnostic,
|
|
||||||
) -> ApiError {
|
|
||||||
ApiError::with_diagnostics(
|
|
||||||
Error::RuntimeOperationFailed {
|
|
||||||
runtime_id,
|
|
||||||
code: diagnostic.code.clone(),
|
|
||||||
message: diagnostic.message.clone(),
|
|
||||||
},
|
|
||||||
vec![RuntimeDiagnostic {
|
|
||||||
code: diagnostic.code,
|
|
||||||
severity: DiagnosticSeverity::Error,
|
|
||||||
message: diagnostic.message,
|
|
||||||
}],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn settings_bad_request(code: &'static str, message: &'static str) -> ApiError {
|
fn settings_bad_request(code: &'static str, message: &'static str) -> ApiError {
|
||||||
Error::RuntimeOperationFailed {
|
Error::RuntimeOperationFailed {
|
||||||
runtime_id: "workspace-backend".to_string(),
|
runtime_id: "workspace-backend".to_string(),
|
||||||
|
|
@ -3475,6 +3580,7 @@ mod tests {
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
use tower::ServiceExt;
|
use tower::ServiceExt;
|
||||||
use worker_runtime::resource::BackendResourceClient;
|
use worker_runtime::resource::BackendResourceClient;
|
||||||
|
use worker_runtime::working_directory::WorkingDirectoryMaterializer;
|
||||||
|
|
||||||
use crate::hosts::{
|
use crate::hosts::{
|
||||||
TicketWorkerRole, WorkerInputKind, WorkerOperationState, WorkerSpawnAcceptanceRequirement,
|
TicketWorkerRole, WorkerInputKind, WorkerOperationState, WorkerSpawnAcceptanceRequirement,
|
||||||
|
|
@ -3731,7 +3837,6 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct DeterministicExecutionBackend {
|
struct DeterministicExecutionBackend {
|
||||||
contexts: std::sync::Mutex<
|
contexts: std::sync::Mutex<
|
||||||
std::collections::HashMap<
|
std::collections::HashMap<
|
||||||
|
|
@ -3739,6 +3844,26 @@ mod tests {
|
||||||
worker_runtime::execution::WorkerExecutionContext,
|
worker_runtime::execution::WorkerExecutionContext,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
|
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DeterministicExecutionBackend {
|
||||||
|
fn default() -> Self {
|
||||||
|
let unique = format!(
|
||||||
|
"yoi-deterministic-wd-{}-{}",
|
||||||
|
std::process::id(),
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_nanos()
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
contexts: std::sync::Mutex::new(std::collections::HashMap::new()),
|
||||||
|
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer::new(
|
||||||
|
std::env::temp_dir().join(unique),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl worker_runtime::execution::WorkerExecutionBackend for DeterministicExecutionBackend {
|
impl worker_runtime::execution::WorkerExecutionBackend for DeterministicExecutionBackend {
|
||||||
|
|
@ -3746,10 +3871,65 @@ mod tests {
|
||||||
"deterministic-workspace-server-test"
|
"deterministic-workspace-server-test"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_working_directory(
|
||||||
|
&self,
|
||||||
|
request: &worker_runtime::catalog::WorkingDirectoryRequest,
|
||||||
|
) -> std::result::Result<
|
||||||
|
worker_runtime::catalog::WorkingDirectoryStatus,
|
||||||
|
worker_runtime::working_directory::WorkingDirectoryDiagnostic,
|
||||||
|
> {
|
||||||
|
Ok(self.materializer.create(request)?.status())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list_working_directories(&self) -> Vec<worker_runtime::catalog::WorkingDirectoryStatus> {
|
||||||
|
self.materializer
|
||||||
|
.list_working_directories()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> std::result::Result<
|
||||||
|
worker_runtime::catalog::WorkingDirectoryStatus,
|
||||||
|
worker_runtime::working_directory::WorkingDirectoryDiagnostic,
|
||||||
|
> {
|
||||||
|
self.materializer
|
||||||
|
.working_directory_status(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_working_directory(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
|
) -> std::result::Result<
|
||||||
|
worker_runtime::catalog::WorkingDirectoryStatus,
|
||||||
|
worker_runtime::working_directory::WorkingDirectoryDiagnostic,
|
||||||
|
> {
|
||||||
|
self.materializer
|
||||||
|
.cleanup_working_directory(working_directory_id)
|
||||||
|
}
|
||||||
|
|
||||||
fn spawn_worker(
|
fn spawn_worker(
|
||||||
&self,
|
&self,
|
||||||
request: worker_runtime::execution::WorkerExecutionSpawnRequest,
|
request: worker_runtime::execution::WorkerExecutionSpawnRequest,
|
||||||
) -> worker_runtime::execution::WorkerExecutionSpawnResult {
|
) -> worker_runtime::execution::WorkerExecutionSpawnResult {
|
||||||
|
let working_directory = match request.request.working_directory.as_ref() {
|
||||||
|
Some(claim) => match self.materializer.bind_working_directory(
|
||||||
|
&claim.working_directory_id,
|
||||||
|
claim.relative_cwd.as_deref(),
|
||||||
|
) {
|
||||||
|
Ok(binding) => Some(binding.status()),
|
||||||
|
Err(diagnostic) => {
|
||||||
|
return worker_runtime::execution::WorkerExecutionSpawnResult::Rejected(
|
||||||
|
worker_runtime::execution::WorkerExecutionResult::rejected(
|
||||||
|
worker_runtime::execution::WorkerExecutionOperation::Spawn,
|
||||||
|
diagnostic.to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
self.contexts
|
self.contexts
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -3760,10 +3940,7 @@ mod tests {
|
||||||
self.backend_id(),
|
self.backend_id(),
|
||||||
),
|
),
|
||||||
run_state: worker_runtime::execution::WorkerExecutionRunState::Idle,
|
run_state: worker_runtime::execution::WorkerExecutionRunState::Idle,
|
||||||
working_directory: request
|
working_directory,
|
||||||
.working_directory
|
|
||||||
.as_ref()
|
|
||||||
.map(|binding| binding.status()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,10 +142,12 @@
|
||||||
creatingWorkingDirectory = true;
|
creatingWorkingDirectory = true;
|
||||||
submitError = null;
|
submitError = null;
|
||||||
try {
|
try {
|
||||||
const response = await fetch(workerApiPath('/working-directories'), {
|
const response = await fetch(
|
||||||
|
workerApiPath(`/runtimes/${encodeURIComponent(runtimeId)}/working-directories`), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'content-type': 'application/json' },
|
headers: { 'content-type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
runtime_id: runtimeId,
|
||||||
repository_id: workingDirectoryRepositoryId,
|
repository_id: workingDirectoryRepositoryId,
|
||||||
selector: workingDirectorySelector || null,
|
selector: workingDirectorySelector || null,
|
||||||
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
|
policy: { dirty_state: 'clean_point_only', cleanup: 'manual_or_worker_stop' },
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ export type BrowserWorkerWorkingDirectorySelection = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserWorkingDirectoryCreateRequest = {
|
export type BrowserWorkingDirectoryCreateRequest = {
|
||||||
|
runtime_id: string;
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
selector?: string | null;
|
selector?: string | null;
|
||||||
policy?: {
|
policy?: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user