refactor: remove working directory allocation terminology
This commit is contained in:
parent
5c33b2f63d
commit
25900f31cf
|
|
@ -90,8 +90,8 @@ pub struct WorkingDirectoryRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WorkingDirectoryAllocationClaim {
|
pub struct WorkingDirectoryClaim {
|
||||||
pub allocation_id: String,
|
pub working_directory_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub relative_cwd: Option<String>,
|
pub relative_cwd: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
@ -107,13 +107,13 @@ pub enum WorkingDirectoryStatusKind {
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WorkingDirectoryCleanupTarget {
|
pub struct WorkingDirectoryCleanupTarget {
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
pub allocation_id: String,
|
pub working_directory_id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WorkingDirectorySummary {
|
pub struct WorkingDirectorySummary {
|
||||||
pub allocation_id: String,
|
pub working_directory_id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub requested_selector: Option<String>,
|
pub requested_selector: Option<String>,
|
||||||
|
|
@ -152,9 +152,9 @@ pub struct CreateWorkerRequest {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub initial_input: Option<WorkerInput>,
|
pub initial_input: Option<WorkerInput>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub working_directory: Option<WorkingDirectoryRequest>,
|
pub working_directory_request: Option<WorkingDirectoryRequest>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
pub working_directory: Option<WorkingDirectoryClaim>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Worker lifecycle status for the in-memory embedded runtime.
|
/// Worker lifecycle status for the in-memory embedded runtime.
|
||||||
|
|
|
||||||
|
|
@ -863,8 +863,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
|
working_directory_request: None,
|
||||||
working_directory: None,
|
working_directory: None,
|
||||||
working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1148,8 +1148,8 @@ mod ws_tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
|
working_directory_request: None,
|
||||||
working_directory: None,
|
working_directory: None,
|
||||||
working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1592,8 +1592,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
|
working_directory_request: None,
|
||||||
working_directory: None,
|
working_directory: None,
|
||||||
working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -375,23 +375,23 @@ where
|
||||||
|
|
||||||
let mut request = request;
|
let mut request = request;
|
||||||
let working_directory = match (
|
let working_directory = match (
|
||||||
|
request.request.working_directory_request.as_ref(),
|
||||||
request.request.working_directory.as_ref(),
|
request.request.working_directory.as_ref(),
|
||||||
request.request.working_directory_allocation.as_ref(),
|
|
||||||
) {
|
) {
|
||||||
(Some(_), Some(_)) => {
|
(Some(_), Some(_)) => {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"worker spawn cannot specify both working_directory and working_directory_allocation",
|
"worker spawn cannot specify both working_directory_request and working_directory",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
(Some(workspace_request), None) => {
|
(Some(working_directory_request), None) => {
|
||||||
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"working directory materialization requested, but no materializer is configured for this runtime backend",
|
"working directory materialization requested, but no materializer is configured for this runtime backend",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
match materializer.materialize(&request.worker_ref, workspace_request) {
|
match materializer.materialize(&request.worker_ref, working_directory_request) {
|
||||||
Ok(binding) => {
|
Ok(binding) => {
|
||||||
request.working_directory = Some(binding.clone());
|
request.working_directory = Some(binding.clone());
|
||||||
Some(binding)
|
Some(binding)
|
||||||
|
|
@ -406,16 +406,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Some(allocation)) => {
|
(None, Some(working_directory)) => {
|
||||||
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||||
WorkerExecutionOperation::Spawn,
|
WorkerExecutionOperation::Spawn,
|
||||||
"working directory allocation requested, but no materializer is configured for this runtime backend",
|
"working directory working_directory requested, but no materializer is configured for this runtime backend",
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
match materializer.bind_allocation(
|
match materializer.bind_working_directory(
|
||||||
&allocation.allocation_id,
|
&working_directory.working_directory_id,
|
||||||
allocation.relative_cwd.as_deref(),
|
working_directory.relative_cwd.as_deref(),
|
||||||
) {
|
) {
|
||||||
Ok(binding) => {
|
Ok(binding) => {
|
||||||
request.working_directory = Some(binding.clone());
|
request.working_directory = Some(binding.clone());
|
||||||
|
|
@ -780,8 +780,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
|
working_directory_request: None,
|
||||||
working_directory: None,
|
working_directory: None,
|
||||||
working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -933,7 +933,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
runtime.store_config_bundle(test_bundle()).unwrap();
|
runtime.store_config_bundle(test_bundle()).unwrap();
|
||||||
let mut request = create_request("chat");
|
let mut request = create_request("chat");
|
||||||
request.working_directory = Some(working_directory_request(repo.path()));
|
request.working_directory_request = Some(working_directory_request(repo.path()));
|
||||||
|
|
||||||
let detail = runtime.create_worker(request).unwrap();
|
let detail = runtime.create_worker(request).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
const WORKING_DIRECTORIES_DIR: &str = "working-directories";
|
const WORKING_DIRECTORIES_DIR: &str = "working-directories";
|
||||||
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
||||||
static NEXT_ALLOCATION_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
static NEXT_WORKING_DIRECTORY_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WorkingDirectoryEvidence {
|
pub struct WorkingDirectoryEvidence {
|
||||||
|
|
@ -27,7 +27,7 @@ pub struct WorkingDirectoryEvidence {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct WorkingDirectoryAllocation {
|
pub struct WorkingDirectory {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub repository_id: String,
|
pub repository_id: String,
|
||||||
pub materializer_kind: MaterializerKind,
|
pub materializer_kind: MaterializerKind,
|
||||||
|
|
@ -38,10 +38,10 @@ pub struct WorkingDirectoryAllocation {
|
||||||
pub status: WorkingDirectoryStatusKind,
|
pub status: WorkingDirectoryStatusKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WorkingDirectoryAllocation {
|
impl WorkingDirectory {
|
||||||
pub fn status_summary(&self) -> WorkingDirectorySummary {
|
pub fn status_summary(&self) -> WorkingDirectorySummary {
|
||||||
WorkingDirectorySummary {
|
WorkingDirectorySummary {
|
||||||
allocation_id: self.id.clone(),
|
working_directory_id: self.id.clone(),
|
||||||
repository_id: self.repository_id.clone(),
|
repository_id: self.repository_id.clone(),
|
||||||
requested_selector: self.evidence.requested_selector.clone(),
|
requested_selector: self.evidence.requested_selector.clone(),
|
||||||
materializer_kind: self.materializer_kind.clone(),
|
materializer_kind: self.materializer_kind.clone(),
|
||||||
|
|
@ -57,10 +57,10 @@ impl WorkingDirectoryAllocation {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct WorkingDirectoryBinding {
|
pub struct WorkingDirectoryBinding {
|
||||||
pub allocation: WorkingDirectoryAllocation,
|
pub working_directory: WorkingDirectory,
|
||||||
pub workspace_root: PathBuf,
|
pub workspace_root: PathBuf,
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
allocation_root: PathBuf,
|
working_directory_root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
source_repository_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,8 +73,8 @@ impl WorkingDirectoryBinding {
|
||||||
&self.cwd
|
&self.cwd
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocation_root(&self) -> &Path {
|
pub fn working_directory_root(&self) -> &Path {
|
||||||
&self.allocation_root
|
&self.working_directory_root
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_repository_path(&self) -> &Path {
|
pub fn source_repository_path(&self) -> &Path {
|
||||||
|
|
@ -83,7 +83,7 @@ impl WorkingDirectoryBinding {
|
||||||
|
|
||||||
pub fn status(&self) -> WorkingDirectoryStatus {
|
pub fn status(&self) -> WorkingDirectoryStatus {
|
||||||
WorkingDirectoryStatus {
|
WorkingDirectoryStatus {
|
||||||
summary: self.allocation.status_summary(),
|
summary: self.working_directory.status_summary(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -118,27 +118,29 @@ pub trait WorkingDirectoryMaterializer: Send + Sync + 'static {
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn preallocate(
|
fn create(
|
||||||
&self,
|
&self,
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn bind_allocation(
|
fn bind_working_directory(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
relative_cwd: Option<&str>,
|
relative_cwd: Option<&str>,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic>;
|
fn list_working_directories(
|
||||||
|
|
||||||
fn allocation_status(
|
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
|
fn working_directory_status(
|
||||||
|
&self,
|
||||||
|
working_directory_id: &str,
|
||||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn cleanup_allocation(
|
fn cleanup_working_directory(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||||
|
|
||||||
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic>;
|
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic>;
|
||||||
|
|
@ -160,7 +162,7 @@ impl LocalGitWorktreeMaterializer {
|
||||||
&self.runtime_root
|
&self.runtime_root
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocation_id(worker_ref: &WorkerRef, repository_id: &str) -> String {
|
fn working_directory_id(worker_ref: &WorkerRef, repository_id: &str) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}-{}-{}",
|
"{}-{}-{}",
|
||||||
sanitize_path_component(worker_ref.runtime_id.as_str()),
|
sanitize_path_component(worker_ref.runtime_id.as_str()),
|
||||||
|
|
@ -169,10 +171,10 @@ impl LocalGitWorktreeMaterializer {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocation_root(&self, allocation_id: &str) -> PathBuf {
|
fn working_directory_root(&self, working_directory_id: &str) -> PathBuf {
|
||||||
self.runtime_root
|
self.runtime_root
|
||||||
.join(WORKING_DIRECTORIES_DIR)
|
.join(WORKING_DIRECTORIES_DIR)
|
||||||
.join(allocation_id)
|
.join(working_directory_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_record(
|
fn write_record(
|
||||||
|
|
@ -180,11 +182,11 @@ impl LocalGitWorktreeMaterializer {
|
||||||
binding: &WorkingDirectoryBinding,
|
binding: &WorkingDirectoryBinding,
|
||||||
) -> Result<(), WorkingDirectoryDiagnostic> {
|
) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
let record = WorkingDirectoryMaterializationRecord {
|
let record = WorkingDirectoryMaterializationRecord {
|
||||||
allocation: binding.allocation.clone(),
|
working_directory: binding.working_directory.clone(),
|
||||||
workspace_root: binding.workspace_root.clone(),
|
workspace_root: binding.workspace_root.clone(),
|
||||||
source_repository_path: binding.source_repository_path.clone(),
|
source_repository_path: binding.source_repository_path.clone(),
|
||||||
};
|
};
|
||||||
let path = binding.allocation_root.join(MATERIALIZATION_RECORD);
|
let path = binding.working_directory_root.join(MATERIALIZATION_RECORD);
|
||||||
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_record_serialize_failed",
|
"working_directory_record_serialize_failed",
|
||||||
|
|
@ -201,38 +203,38 @@ impl LocalGitWorktreeMaterializer {
|
||||||
|
|
||||||
fn read_binding(
|
fn read_binding(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_root = self.allocation_root(allocation_id);
|
let working_directory_root = self.working_directory_root(working_directory_id);
|
||||||
let path = allocation_root.join(MATERIALIZATION_RECORD);
|
let path = working_directory_root.join(MATERIALIZATION_RECORD);
|
||||||
let raw = fs::read(&path).map_err(|_| {
|
let raw = fs::read(&path).map_err(|_| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_allocation_not_found",
|
"working_directory_not_found",
|
||||||
"working directory allocation was not found",
|
"working directory working_directory was not found",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let record: WorkingDirectoryMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
let record: WorkingDirectoryMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_record_invalid",
|
"working_directory_record_invalid",
|
||||||
"working directory allocation record is invalid; backend-private path details were omitted",
|
"working directory working_directory record is invalid; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
Ok(WorkingDirectoryBinding {
|
Ok(WorkingDirectoryBinding {
|
||||||
allocation: record.allocation,
|
working_directory: record.working_directory,
|
||||||
workspace_root: record.workspace_root.clone(),
|
workspace_root: record.workspace_root.clone(),
|
||||||
cwd: record.workspace_root,
|
cwd: record.workspace_root,
|
||||||
allocation_root,
|
working_directory_root,
|
||||||
source_repository_path: record.source_repository_path,
|
source_repository_path: record.source_repository_path,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn materialize_with_allocation_id(
|
fn materialize_with_working_directory_id(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: String,
|
working_directory_id: String,
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
cleanup_policy: &str,
|
cleanup_policy: &str,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(&allocation_id)?;
|
validate_working_directory_id(&working_directory_id)?;
|
||||||
if request.materializer != MaterializerKind::LocalGitWorktree {
|
if request.materializer != MaterializerKind::LocalGitWorktree {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_materializer_unsupported",
|
"working_directory_materializer_unsupported",
|
||||||
|
|
@ -299,26 +301,26 @@ impl LocalGitWorktreeMaterializer {
|
||||||
.map(|value| value.trim().to_string())
|
.map(|value| value.trim().to_string())
|
||||||
.filter(|value| !value.is_empty());
|
.filter(|value| !value.is_empty());
|
||||||
|
|
||||||
let allocation_root = self.allocation_root(&allocation_id);
|
let working_directory_root = self.working_directory_root(&working_directory_id);
|
||||||
let workspace_root = allocation_root
|
let workspace_root = working_directory_root
|
||||||
.join("root")
|
.join("root")
|
||||||
.join(sanitize_path_component(&request.repository.id));
|
.join(sanitize_path_component(&request.repository.id));
|
||||||
if workspace_root.exists() {
|
if workspace_root.exists() {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_allocation_exists",
|
"working_directory_exists",
|
||||||
"working directory allocation target already exists; cleanup or choose a new allocation",
|
"working directory working_directory target already exists; cleanup or choose a new working_directory",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
fs::create_dir_all(workspace_root.parent().ok_or_else(|| {
|
fs::create_dir_all(workspace_root.parent().ok_or_else(|| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_invalid_target",
|
"working_directory_invalid_target",
|
||||||
"working directory allocation target has no parent directory",
|
"working directory working_directory target has no parent directory",
|
||||||
)
|
)
|
||||||
})?)
|
})?)
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_create_failed",
|
"working_directory_create_failed",
|
||||||
"failed to create working directory allocation directory; backend-private path details were omitted",
|
"failed to create working directory working_directory directory; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
@ -334,8 +336,8 @@ impl LocalGitWorktreeMaterializer {
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let allocation = WorkingDirectoryAllocation {
|
let working_directory = WorkingDirectory {
|
||||||
id: allocation_id.clone(),
|
id: working_directory_id.clone(),
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
materializer_kind: MaterializerKind::LocalGitWorktree,
|
||||||
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
||||||
|
|
@ -353,17 +355,17 @@ impl LocalGitWorktreeMaterializer {
|
||||||
},
|
},
|
||||||
cleanup_target: WorkingDirectoryCleanupTarget {
|
cleanup_target: WorkingDirectoryCleanupTarget {
|
||||||
kind: "git_worktree".to_string(),
|
kind: "git_worktree".to_string(),
|
||||||
allocation_id,
|
working_directory_id,
|
||||||
repository_id: request.repository.id.clone(),
|
repository_id: request.repository.id.clone(),
|
||||||
},
|
},
|
||||||
cleanup_policy: cleanup_policy.to_string(),
|
cleanup_policy: cleanup_policy.to_string(),
|
||||||
status: WorkingDirectoryStatusKind::Active,
|
status: WorkingDirectoryStatusKind::Active,
|
||||||
};
|
};
|
||||||
let binding = WorkingDirectoryBinding {
|
let binding = WorkingDirectoryBinding {
|
||||||
allocation,
|
working_directory,
|
||||||
workspace_root: workspace_root.clone(),
|
workspace_root: workspace_root.clone(),
|
||||||
cwd: workspace_root,
|
cwd: workspace_root,
|
||||||
allocation_root,
|
working_directory_root,
|
||||||
source_repository_path: source_root,
|
source_repository_path: source_root,
|
||||||
};
|
};
|
||||||
self.write_record(&binding)?;
|
self.write_record(&binding)?;
|
||||||
|
|
@ -377,36 +379,46 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
worker_ref: &WorkerRef,
|
worker_ref: &WorkerRef,
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_id = Self::allocation_id(worker_ref, &request.repository.id);
|
let working_directory_id = Self::working_directory_id(worker_ref, &request.repository.id);
|
||||||
self.materialize_with_allocation_id(allocation_id, request, "remove_on_worker_stop")
|
self.materialize_with_working_directory_id(
|
||||||
|
working_directory_id,
|
||||||
|
request,
|
||||||
|
"remove_on_worker_stop",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preallocate(
|
fn create(
|
||||||
&self,
|
&self,
|
||||||
request: &WorkingDirectoryRequest,
|
request: &WorkingDirectoryRequest,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
let allocation_id = next_allocation_id(&request.repository.id);
|
let working_directory_id = next_working_directory_id(&request.repository.id);
|
||||||
self.materialize_with_allocation_id(allocation_id, request, "manual_or_worker_stop")
|
self.materialize_with_working_directory_id(
|
||||||
|
working_directory_id,
|
||||||
|
request,
|
||||||
|
"manual_or_worker_stop",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_allocation(
|
fn bind_working_directory(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
relative_cwd: Option<&str>,
|
relative_cwd: Option<&str>,
|
||||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_working_directory_id(working_directory_id)?;
|
||||||
let binding = self.read_binding(allocation_id)?;
|
let binding = self.read_binding(working_directory_id)?;
|
||||||
if binding.allocation.status != WorkingDirectoryStatusKind::Active {
|
if binding.working_directory.status != WorkingDirectoryStatusKind::Active {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_not_active",
|
"working_directory_not_active",
|
||||||
"working directory allocation is not active",
|
"working directory working_directory is not active",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?;
|
let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?;
|
||||||
Ok(WorkingDirectoryBinding { cwd, ..binding })
|
Ok(WorkingDirectoryBinding { cwd, ..binding })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic> {
|
fn list_working_directories(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic> {
|
||||||
let root = self.runtime_root.join(WORKING_DIRECTORIES_DIR);
|
let root = self.runtime_root.join(WORKING_DIRECTORIES_DIR);
|
||||||
let entries = match fs::read_dir(&root) {
|
let entries = match fs::read_dir(&root) {
|
||||||
Ok(entries) => entries,
|
Ok(entries) => entries,
|
||||||
|
|
@ -414,7 +426,7 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_list_failed",
|
"working_directory_list_failed",
|
||||||
"failed to list working directory allocations; backend-private path details were omitted",
|
"failed to list working directory working_directories; backend-private path details were omitted",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -427,43 +439,46 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
if !file_type.is_dir() {
|
if !file_type.is_dir() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let allocation_id = entry.file_name().to_string_lossy().to_string();
|
let working_directory_id = entry.file_name().to_string_lossy().to_string();
|
||||||
if validate_allocation_id(&allocation_id).is_err() {
|
if validate_working_directory_id(&working_directory_id).is_err() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(status) = self.allocation_status(&allocation_id) {
|
if let Ok(status) = self.working_directory_status(&working_directory_id) {
|
||||||
statuses.push(status);
|
statuses.push(status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statuses
|
statuses.sort_by(|left, right| {
|
||||||
.sort_by(|left, right| left.summary.allocation_id.cmp(&right.summary.allocation_id));
|
left.summary
|
||||||
|
.working_directory_id
|
||||||
|
.cmp(&right.summary.working_directory_id)
|
||||||
|
});
|
||||||
Ok(statuses)
|
Ok(statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocation_status(
|
fn working_directory_status(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_working_directory_id(working_directory_id)?;
|
||||||
Ok(self.read_binding(allocation_id)?.status())
|
Ok(self.read_binding(working_directory_id)?.status())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup_allocation(
|
fn cleanup_working_directory(
|
||||||
&self,
|
&self,
|
||||||
allocation_id: &str,
|
working_directory_id: &str,
|
||||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||||
validate_allocation_id(allocation_id)?;
|
validate_working_directory_id(working_directory_id)?;
|
||||||
let binding = self.read_binding(allocation_id)?;
|
let binding = self.read_binding(working_directory_id)?;
|
||||||
self.cleanup(&binding)?;
|
self.cleanup(&binding)?;
|
||||||
self.allocation_status(allocation_id)
|
self.working_directory_status(working_directory_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic> {
|
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
let mut allocation = binding.allocation.clone();
|
let mut working_directory = binding.working_directory.clone();
|
||||||
let allocation_root = binding.allocation_root.canonicalize().map_err(|_| {
|
let working_directory_root = binding.working_directory_root.canonicalize().map_err(|_| {
|
||||||
WorkingDirectoryDiagnostic::new(
|
WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_cleanup_target_invalid",
|
"working_directory_cleanup_target_invalid",
|
||||||
"working directory allocation root is unavailable; backend-private path details were omitted",
|
"working directory working directory root is unavailable; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let workspace_root = binding.workspace_root.canonicalize().map_err(|_| {
|
let workspace_root = binding.workspace_root.canonicalize().map_err(|_| {
|
||||||
|
|
@ -472,10 +487,10 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
"working directory root is unavailable; backend-private path details were omitted",
|
"working directory root is unavailable; backend-private path details were omitted",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !workspace_root.starts_with(&allocation_root) {
|
if !workspace_root.starts_with(&working_directory_root) {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_cleanup_escape_rejected",
|
"working_directory_cleanup_escape_rejected",
|
||||||
"working directory cleanup target is outside the allocation root",
|
"working directory cleanup target is outside the working directory root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let workspace_root_arg = path_str(&workspace_root)?;
|
let workspace_root_arg = path_str(&workspace_root)?;
|
||||||
|
|
@ -495,16 +510,16 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
allocation.status = if remove_result.is_ok() {
|
working_directory.status = if remove_result.is_ok() {
|
||||||
WorkingDirectoryStatusKind::Removed
|
WorkingDirectoryStatusKind::Removed
|
||||||
} else {
|
} else {
|
||||||
WorkingDirectoryStatusKind::CleanupPending
|
WorkingDirectoryStatusKind::CleanupPending
|
||||||
};
|
};
|
||||||
let updated = WorkingDirectoryBinding {
|
let updated = WorkingDirectoryBinding {
|
||||||
allocation,
|
working_directory,
|
||||||
workspace_root: binding.workspace_root.clone(),
|
workspace_root: binding.workspace_root.clone(),
|
||||||
cwd: binding.cwd.clone(),
|
cwd: binding.cwd.clone(),
|
||||||
allocation_root: binding.allocation_root.clone(),
|
working_directory_root: binding.working_directory_root.clone(),
|
||||||
source_repository_path: binding.source_repository_path.clone(),
|
source_repository_path: binding.source_repository_path.clone(),
|
||||||
};
|
};
|
||||||
let _ = self.write_record(&updated);
|
let _ = self.write_record(&updated);
|
||||||
|
|
@ -514,7 +529,7 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
struct WorkingDirectoryMaterializationRecord {
|
struct WorkingDirectoryMaterializationRecord {
|
||||||
allocation: WorkingDirectoryAllocation,
|
working_directory: WorkingDirectory,
|
||||||
workspace_root: PathBuf,
|
workspace_root: PathBuf,
|
||||||
source_repository_path: PathBuf,
|
source_repository_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
@ -586,29 +601,31 @@ fn sanitize_path_component(value: &str) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_allocation_id(repository_id: &str) -> String {
|
fn next_working_directory_id(repository_id: &str) -> String {
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.map(|duration| duration.as_millis())
|
.map(|duration| duration.as_millis())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let sequence = NEXT_ALLOCATION_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
let sequence = NEXT_WORKING_DIRECTORY_SEQUENCE.fetch_add(1, Ordering::Relaxed);
|
||||||
format!(
|
format!(
|
||||||
"alloc-{now}-{sequence}-{}",
|
"alloc-{now}-{sequence}-{}",
|
||||||
sanitize_path_component(repository_id)
|
sanitize_path_component(repository_id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_allocation_id(allocation_id: &str) -> Result<(), WorkingDirectoryDiagnostic> {
|
fn validate_working_directory_id(
|
||||||
let sanitized = sanitize_path_component(allocation_id);
|
working_directory_id: &str,
|
||||||
if allocation_id.is_empty()
|
) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||||
|| allocation_id != sanitized
|
let sanitized = sanitize_path_component(working_directory_id);
|
||||||
|| allocation_id.contains(std::path::MAIN_SEPARATOR)
|
if working_directory_id.is_empty()
|
||||||
|| allocation_id.contains('/')
|
|| working_directory_id != sanitized
|
||||||
|| allocation_id.contains('\\')
|
|| working_directory_id.contains(std::path::MAIN_SEPARATOR)
|
||||||
|
|| working_directory_id.contains('/')
|
||||||
|
|| working_directory_id.contains('\\')
|
||||||
{
|
{
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_allocation_id_invalid",
|
"working_directory_id_invalid",
|
||||||
"working directory allocation id is invalid",
|
"working directory working_directory id is invalid",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -639,7 +656,7 @@ fn validate_relative_cwd(
|
||||||
{
|
{
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_relative_cwd_invalid",
|
"working_directory_relative_cwd_invalid",
|
||||||
"working directory relative_cwd must be a relative path inside the allocation root",
|
"working directory relative_cwd must be a relative path inside the working directory root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let target = workspace_root
|
let target = workspace_root
|
||||||
|
|
@ -654,7 +671,7 @@ fn validate_relative_cwd(
|
||||||
if !target.starts_with(&workspace_root) || !target.is_dir() {
|
if !target.starts_with(&workspace_root) || !target.is_dir() {
|
||||||
return Err(WorkingDirectoryDiagnostic::new(
|
return Err(WorkingDirectoryDiagnostic::new(
|
||||||
"working_directory_relative_cwd_escape_rejected",
|
"working_directory_relative_cwd_escape_rejected",
|
||||||
"working directory relative_cwd must resolve to a directory inside the allocation root",
|
"working directory relative_cwd must resolve to a directory inside the working directory root",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(target)
|
Ok(target)
|
||||||
|
|
@ -728,16 +745,16 @@ mod tests {
|
||||||
"worktree should be detached, got {branch}"
|
"worktree should be detached, got {branch}"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
binding.allocation.materializer_kind,
|
binding.working_directory.materializer_kind,
|
||||||
MaterializerKind::LocalGitWorktree
|
MaterializerKind::LocalGitWorktree
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
binding.allocation.dirty_state_policy,
|
binding.working_directory.dirty_state_policy,
|
||||||
DirtyStatePolicy::CleanPointOnly
|
DirtyStatePolicy::CleanPointOnly
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
binding
|
binding
|
||||||
.allocation_root()
|
.working_directory_root()
|
||||||
.join(MATERIALIZATION_RECORD)
|
.join(MATERIALIZATION_RECORD)
|
||||||
.exists()
|
.exists()
|
||||||
);
|
);
|
||||||
|
|
@ -805,7 +822,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preallocated_allocation_binds_safe_relative_cwd_and_lists_without_paths() {
|
fn working_directory_binds_safe_relative_cwd_and_lists_without_paths() {
|
||||||
let repo = create_clean_repo();
|
let repo = create_clean_repo();
|
||||||
fs::create_dir_all(repo.path().join("crates/yoi")).unwrap();
|
fs::create_dir_all(repo.path().join("crates/yoi")).unwrap();
|
||||||
fs::write(repo.path().join("crates/yoi/lib.rs"), "// ok\n").unwrap();
|
fs::write(repo.path().join("crates/yoi/lib.rs"), "// ok\n").unwrap();
|
||||||
|
|
@ -813,15 +830,18 @@ mod tests {
|
||||||
git(repo.path(), &["commit", "-m", "add crate"]);
|
git(repo.path(), &["commit", "-m", "add crate"]);
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path());
|
let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path());
|
||||||
let allocation = materializer.preallocate(&request(repo.path())).unwrap();
|
let working_directory = materializer.create(&request(repo.path())).unwrap();
|
||||||
|
|
||||||
let bound = materializer
|
let bound = materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("crates/yoi"))
|
.bind_working_directory(&working_directory.working_directory.id, Some("crates/yoi"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(bound.cwd.file_name().unwrap(), "yoi");
|
assert_eq!(bound.cwd.file_name().unwrap(), "yoi");
|
||||||
let listed = materializer.list_allocations().unwrap();
|
let listed = materializer.list_working_directories().unwrap();
|
||||||
assert_eq!(listed.len(), 1);
|
assert_eq!(listed.len(), 1);
|
||||||
assert_eq!(listed[0].summary.allocation_id, allocation.allocation.id);
|
assert_eq!(
|
||||||
|
listed[0].summary.working_directory_id,
|
||||||
|
working_directory.working_directory.id
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
listed[0].summary.requested_selector.as_deref(),
|
listed[0].summary.requested_selector.as_deref(),
|
||||||
Some("HEAD")
|
Some("HEAD")
|
||||||
|
|
@ -837,32 +857,35 @@ mod tests {
|
||||||
git(repo.path(), &["commit", "-m", "add inside"]);
|
git(repo.path(), &["commit", "-m", "add inside"]);
|
||||||
let runtime_root = tempfile::tempdir().unwrap();
|
let runtime_root = tempfile::tempdir().unwrap();
|
||||||
let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path());
|
let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path());
|
||||||
let allocation = materializer.preallocate(&request(repo.path())).unwrap();
|
let working_directory = materializer.create(&request(repo.path())).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("/tmp"))
|
.bind_working_directory(&working_directory.working_directory.id, Some("/tmp"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"working_directory_relative_cwd_invalid"
|
"working_directory_relative_cwd_invalid"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("../outside"))
|
.bind_working_directory(&working_directory.working_directory.id, Some("../outside"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"working_directory_relative_cwd_invalid"
|
"working_directory_relative_cwd_invalid"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("missing"))
|
.bind_working_directory(&working_directory.working_directory.id, Some("missing"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"working_directory_relative_cwd_unavailable"
|
"working_directory_relative_cwd_unavailable"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("inside/file.txt"))
|
.bind_working_directory(
|
||||||
|
&working_directory.working_directory.id,
|
||||||
|
Some("inside/file.txt")
|
||||||
|
)
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"working_directory_relative_cwd_escape_rejected"
|
"working_directory_relative_cwd_escape_rejected"
|
||||||
|
|
@ -870,10 +893,11 @@ mod tests {
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
std::os::unix::fs::symlink("/tmp", allocation.workspace_root().join("escape")).unwrap();
|
std::os::unix::fs::symlink("/tmp", working_directory.workspace_root().join("escape"))
|
||||||
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
materializer
|
materializer
|
||||||
.bind_allocation(&allocation.allocation.id, Some("escape"))
|
.bind_working_directory(&working_directory.working_directory.id, Some("escape"))
|
||||||
.unwrap_err()
|
.unwrap_err()
|
||||||
.code,
|
.code,
|
||||||
"working_directory_relative_cwd_escape_rejected"
|
"working_directory_relative_cwd_escape_rejected"
|
||||||
|
|
@ -894,8 +918,12 @@ mod tests {
|
||||||
materializer.cleanup(&binding).unwrap();
|
materializer.cleanup(&binding).unwrap();
|
||||||
|
|
||||||
assert!(!workspace_root.exists());
|
assert!(!workspace_root.exists());
|
||||||
let raw =
|
let raw = fs::read_to_string(
|
||||||
fs::read_to_string(binding.allocation_root().join(MATERIALIZATION_RECORD)).unwrap();
|
binding
|
||||||
|
.working_directory_root()
|
||||||
|
.join(MATERIALIZATION_RECORD),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert!(raw.contains("removed"));
|
assert!(raw.contains("removed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -387,9 +387,9 @@ fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState {
|
||||||
},
|
},
|
||||||
profile: Some(selector),
|
profile: Some(selector),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,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, WorkingDirectoryAllocationClaim, WorkingDirectoryRequest,
|
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
|
||||||
WorkingDirectorySummary,
|
WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use worker_runtime::config_bundle::{
|
use worker_runtime::config_bundle::{
|
||||||
|
|
@ -295,15 +295,15 @@ pub struct WorkerSpawnRequest {
|
||||||
pub profile: Option<ProfileSelector>,
|
pub profile: Option<ProfileSelector>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub initial_input: Option<EmbeddedWorkerInput>,
|
pub initial_input: Option<EmbeddedWorkerInput>,
|
||||||
/// Optional safe working-directory selector. The Workspace server resolves
|
/// Optional safe working-directory creation request. The Workspace server resolves
|
||||||
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
|
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
|
||||||
/// repositories before calling a host.
|
/// repositories before calling a host.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
pub working_directory: Option<WorkerSpawnWorkingDirectoryRequest>,
|
pub working_directory_request: Option<WorkerSpawnWorkingDirectoryRequest>,
|
||||||
#[serde(skip, default)]
|
#[serde(skip, default)]
|
||||||
pub resolved_working_directory: Option<WorkingDirectoryRequest>,
|
pub resolved_working_directory_request: Option<WorkingDirectoryRequest>,
|
||||||
#[serde(skip, default)]
|
#[serde(skip, default)]
|
||||||
pub resolved_working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
pub resolved_working_directory: Option<WorkingDirectoryClaim>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
|
@ -1356,8 +1356,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||||
profile,
|
profile,
|
||||||
config_bundle,
|
config_bundle,
|
||||||
initial_input: request.initial_input.clone(),
|
initial_input: request.initial_input.clone(),
|
||||||
|
working_directory_request: request.resolved_working_directory_request.clone(),
|
||||||
working_directory: request.resolved_working_directory.clone(),
|
working_directory: request.resolved_working_directory.clone(),
|
||||||
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
|
||||||
};
|
};
|
||||||
match self.runtime.create_worker(create_request) {
|
match self.runtime.create_worker(create_request) {
|
||||||
Ok(detail) => {
|
Ok(detail) => {
|
||||||
|
|
@ -2032,8 +2032,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||||
profile,
|
profile,
|
||||||
config_bundle,
|
config_bundle,
|
||||||
initial_input: request.initial_input.clone(),
|
initial_input: request.initial_input.clone(),
|
||||||
|
working_directory_request: request.resolved_working_directory_request.clone(),
|
||||||
working_directory: request.resolved_working_directory.clone(),
|
working_directory: request.resolved_working_directory.clone(),
|
||||||
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
|
||||||
};
|
};
|
||||||
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
||||||
Ok(response) => WorkerSpawnResult {
|
Ok(response) => WorkerSpawnResult {
|
||||||
|
|
@ -3191,9 +3191,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3323,9 +3323,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -3425,9 +3425,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -3456,9 +3456,9 @@ mod tests {
|
||||||
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ use crate::store::{ControlPlaneStore, WorkspaceRecord};
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
use worker_runtime::catalog::{
|
use worker_runtime::catalog::{
|
||||||
ConfigBundleRef, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
ConfigBundleRef, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
||||||
RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryAllocationClaim,
|
RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryClaim,
|
||||||
WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectorySummary,
|
WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectorySummary,
|
||||||
};
|
};
|
||||||
use worker_runtime::config_bundle::ConfigBundle;
|
use worker_runtime::config_bundle::ConfigBundle;
|
||||||
|
|
@ -275,7 +275,7 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||||
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
||||||
)
|
)
|
||||||
.route(
|
.route(
|
||||||
"/api/w/{workspace_id}/working-directories/{allocation_id}",
|
"/api/w/{workspace_id}/working-directories/{working_directory_id}",
|
||||||
get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory),
|
get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory),
|
||||||
)
|
)
|
||||||
.route("/api/runtimes", get(list_runtimes))
|
.route("/api/runtimes", get(list_runtimes))
|
||||||
|
|
@ -626,7 +626,7 @@ pub struct BrowserWorkingDirectoryDetailResponse {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct BrowserWorkerWorkingDirectorySelection {
|
pub struct BrowserWorkerWorkingDirectorySelection {
|
||||||
pub allocation_id: String,
|
pub working_directory_id: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub relative_cwd: Option<String>,
|
pub relative_cwd: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
@ -744,7 +744,7 @@ struct ScopedRuntimePath {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct ScopedWorkingDirectoryPath {
|
struct ScopedWorkingDirectoryPath {
|
||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
allocation_id: String,
|
working_directory_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -903,10 +903,10 @@ 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 workspace_request = working_directory_request_for_browser(&api, request)?;
|
let working_directory_request = working_directory_request_for_browser(&api, request)?;
|
||||||
let binding = api
|
let binding = api
|
||||||
.working_directory_materializer
|
.working_directory_materializer
|
||||||
.preallocate(&workspace_request)
|
.create(&working_directory_request)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||||
|
|
@ -928,7 +928,7 @@ async fn scoped_working_directory_detail(
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
let status = api
|
||||||
.working_directory_materializer
|
.working_directory_materializer
|
||||||
.allocation_status(&path.allocation_id)
|
.working_directory_status(&path.working_directory_id)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||||
|
|
@ -950,7 +950,7 @@ async fn scoped_cleanup_working_directory(
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
let status = api
|
let status = api
|
||||||
.working_directory_materializer
|
.working_directory_materializer
|
||||||
.cleanup_allocation(&path.allocation_id)
|
.cleanup_working_directory(&path.working_directory_id)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
|
||||||
|
|
@ -1621,28 +1621,28 @@ async fn create_workspace_worker(
|
||||||
content: initial_text,
|
content: initial_text,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let resolved_working_directory_allocation =
|
let resolved_working_directory =
|
||||||
request
|
request
|
||||||
.working_directory
|
.working_directory
|
||||||
.map(|selection| WorkingDirectoryAllocationClaim {
|
.map(|selection| WorkingDirectoryClaim {
|
||||||
allocation_id: selection.allocation_id,
|
working_directory_id: selection.working_directory_id,
|
||||||
relative_cwd: selection.relative_cwd,
|
relative_cwd: selection.relative_cwd,
|
||||||
});
|
});
|
||||||
if resolved_working_directory_allocation.is_some()
|
if resolved_working_directory.is_some() && request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID {
|
||||||
&& request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID
|
|
||||||
{
|
|
||||||
return Err(Error::RuntimeOperationFailed {
|
return Err(Error::RuntimeOperationFailed {
|
||||||
runtime_id: request.runtime_id.clone(),
|
runtime_id: request.runtime_id.clone(),
|
||||||
code: "working_directory_runtime_unsupported".to_string(),
|
code: "working_directory_runtime_unsupported".to_string(),
|
||||||
message: "preallocated working directories are only supported by the embedded local runtime in v0".to_string(),
|
message:
|
||||||
|
"created working directories are only supported by the embedded local runtime in v0"
|
||||||
|
.to_string(),
|
||||||
}
|
}
|
||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
if let Some(allocation) = resolved_working_directory_allocation.as_ref() {
|
if let Some(working_directory) = resolved_working_directory.as_ref() {
|
||||||
api.working_directory_materializer
|
api.working_directory_materializer
|
||||||
.bind_allocation(
|
.bind_working_directory(
|
||||||
&allocation.allocation_id,
|
&working_directory.working_directory_id,
|
||||||
allocation.relative_cwd.as_deref(),
|
working_directory.relative_cwd.as_deref(),
|
||||||
)
|
)
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
working_directory_api_error(request.runtime_id.clone(), diagnostic)
|
working_directory_api_error(request.runtime_id.clone(), diagnostic)
|
||||||
|
|
@ -1660,9 +1660,9 @@ async fn create_workspace_worker(
|
||||||
},
|
},
|
||||||
profile: Some(profile_selector),
|
profile: Some(profile_selector),
|
||||||
initial_input,
|
initial_input,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory_allocation,
|
resolved_working_directory,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|err| err.into_error())?;
|
.map_err(|err| err.into_error())?;
|
||||||
|
|
@ -1751,8 +1751,8 @@ async fn create_runtime_worker(
|
||||||
AxumPath(runtime_id): AxumPath<String>,
|
AxumPath(runtime_id): AxumPath<String>,
|
||||||
Json(mut request): Json<WorkerSpawnRequest>,
|
Json(mut request): Json<WorkerSpawnRequest>,
|
||||||
) -> ApiResult<Json<WorkerSpawnResult>> {
|
) -> ApiResult<Json<WorkerSpawnResult>> {
|
||||||
request.resolved_working_directory = request
|
request.resolved_working_directory_request = request
|
||||||
.working_directory
|
.working_directory_request
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|working_directory| {
|
.map(|working_directory| {
|
||||||
configured_working_directory_request(&api.config, working_directory)
|
configured_working_directory_request(&api.config, working_directory)
|
||||||
|
|
@ -2713,7 +2713,7 @@ 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
|
api.working_directory_materializer
|
||||||
.list_allocations()
|
.list_working_directories()
|
||||||
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
||||||
.map_err(|diagnostic| {
|
.map_err(|diagnostic| {
|
||||||
ApiError::from(Error::RuntimeOperationFailed {
|
ApiError::from(Error::RuntimeOperationFailed {
|
||||||
|
|
@ -3418,8 +3418,8 @@ mod tests {
|
||||||
digest: bundle.metadata.digest,
|
digest: bundle.metadata.digest,
|
||||||
},
|
},
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
|
working_directory_request: None,
|
||||||
working_directory: None,
|
working_directory: None,
|
||||||
working_directory_allocation: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3435,7 +3435,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn browser_working_directory_preallocate_list_detail_and_cleanup_are_path_safe() {
|
async fn browser_working_directory_create_list_detail_and_cleanup_are_path_safe() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
init_clean_git_workspace(dir.path());
|
init_clean_git_workspace(dir.path());
|
||||||
let app = test_app(dir.path()).await;
|
let app = test_app(dir.path()).await;
|
||||||
|
|
@ -3451,7 +3451,7 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let allocation_id = created["item"]["allocation_id"]
|
let working_directory_id = created["item"]["working_directory_id"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
@ -3463,11 +3463,14 @@ mod tests {
|
||||||
|
|
||||||
let list = get_json(app.clone(), &workspace_path).await;
|
let list = get_json(app.clone(), &workspace_path).await;
|
||||||
assert_eq!(list["items"].as_array().unwrap().len(), 1);
|
assert_eq!(list["items"].as_array().unwrap().len(), 1);
|
||||||
assert_eq!(list["items"][0]["allocation_id"], allocation_id);
|
assert_eq!(
|
||||||
|
list["items"][0]["working_directory_id"],
|
||||||
|
working_directory_id
|
||||||
|
);
|
||||||
|
|
||||||
let detail_path = format!("{workspace_path}/{allocation_id}");
|
let detail_path = format!("{workspace_path}/{working_directory_id}");
|
||||||
let detail = get_json(app.clone(), &detail_path).await;
|
let detail = get_json(app.clone(), &detail_path).await;
|
||||||
assert_eq!(detail["item"]["allocation_id"], allocation_id);
|
assert_eq!(detail["item"]["working_directory_id"], working_directory_id);
|
||||||
|
|
||||||
let removed = request_json(app, "DELETE", &detail_path, None, StatusCode::OK).await;
|
let removed = request_json(app, "DELETE", &detail_path, None, StatusCode::OK).await;
|
||||||
assert_eq!(removed["item"]["status"], "removed");
|
assert_eq!(removed["item"]["status"], "removed");
|
||||||
|
|
@ -3489,7 +3492,7 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let allocation_id = created["item"]["allocation_id"].as_str().unwrap();
|
let working_directory_id = created["item"]["working_directory_id"].as_str().unwrap();
|
||||||
|
|
||||||
let response = request_json(
|
let response = request_json(
|
||||||
app,
|
app,
|
||||||
|
|
@ -3501,7 +3504,7 @@ mod tests {
|
||||||
"profile": "builtin:coder",
|
"profile": "builtin:coder",
|
||||||
"initial_text": "",
|
"initial_text": "",
|
||||||
"working_directory": {
|
"working_directory": {
|
||||||
"allocation_id": allocation_id,
|
"working_directory_id": working_directory_id,
|
||||||
"relative_cwd": "../escape"
|
"relative_cwd": "../escape"
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
|
|
@ -3857,7 +3860,7 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
"working_directory": {
|
"working_directory_request": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"local_path": dir.path().display().to_string()
|
"local_path": dir.path().display().to_string()
|
||||||
}
|
}
|
||||||
|
|
@ -3892,7 +3895,7 @@ mod tests {
|
||||||
"kind": "run_accepted",
|
"kind": "run_accepted",
|
||||||
"expected_segments": 0
|
"expected_segments": 0
|
||||||
},
|
},
|
||||||
"working_directory": {
|
"working_directory_request": {
|
||||||
"repository_id": TEST_REPOSITORY_ID,
|
"repository_id": TEST_REPOSITORY_ID,
|
||||||
"selector": "HEAD"
|
"selector": "HEAD"
|
||||||
}
|
}
|
||||||
|
|
@ -4379,9 +4382,9 @@ mod tests {
|
||||||
},
|
},
|
||||||
profile: None,
|
profile: None,
|
||||||
initial_input: None,
|
initial_input: None,
|
||||||
working_directory: None,
|
working_directory_request: None,
|
||||||
|
resolved_working_directory_request: None,
|
||||||
resolved_working_directory: None,
|
resolved_working_directory: None,
|
||||||
resolved_working_directory_allocation: None,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("spawn worker");
|
.expect("spawn worker");
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
let runtimeId = $state('');
|
let runtimeId = $state('');
|
||||||
let profile = $state('builtin:coder');
|
let profile = $state('builtin:coder');
|
||||||
let initialText = $state('');
|
let initialText = $state('');
|
||||||
let workingDirectoryAllocationId = $state('');
|
let workingDirectoryId = $state('');
|
||||||
let workingDirectoryRepositoryId = $state('');
|
let workingDirectoryRepositoryId = $state('');
|
||||||
let workingDirectorySelector = $state('HEAD');
|
let workingDirectorySelector = $state('HEAD');
|
||||||
let relativeCwd = $state('');
|
let relativeCwd = $state('');
|
||||||
|
|
@ -114,7 +114,7 @@
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
profile,
|
profile,
|
||||||
initial_text: initialText,
|
initial_text: initialText,
|
||||||
working_directory_allocation_id: workingDirectoryAllocationId,
|
working_directory_id: workingDirectoryId,
|
||||||
working_directory_repository_id: workingDirectoryRepositoryId,
|
working_directory_repository_id: workingDirectoryRepositoryId,
|
||||||
working_directory_selector: workingDirectorySelector,
|
working_directory_selector: workingDirectorySelector,
|
||||||
relative_cwd: relativeCwd,
|
relative_cwd: relativeCwd,
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
runtimeId = form.runtime_id;
|
runtimeId = form.runtime_id;
|
||||||
displayName = form.display_name;
|
displayName = form.display_name;
|
||||||
profile = form.profile;
|
profile = form.profile;
|
||||||
workingDirectoryAllocationId = form.working_directory_allocation_id;
|
workingDirectoryId = form.working_directory_id;
|
||||||
workingDirectoryRepositoryId = form.working_directory_repository_id;
|
workingDirectoryRepositoryId = form.working_directory_repository_id;
|
||||||
workingDirectorySelector = form.working_directory_selector;
|
workingDirectorySelector = form.working_directory_selector;
|
||||||
relativeCwd = form.relative_cwd;
|
relativeCwd = form.relative_cwd;
|
||||||
|
|
@ -158,9 +158,9 @@
|
||||||
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
|
const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse;
|
||||||
const items = options?.working_directories ?? [];
|
const items = options?.working_directories ?? [];
|
||||||
options = options
|
options = options
|
||||||
? { ...options, working_directories: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] }
|
? { ...options, working_directories: [...items.filter((item) => item.working_directory_id !== payload.item.working_directory_id), payload.item] }
|
||||||
: options;
|
: options;
|
||||||
workingDirectoryAllocationId = payload.item.allocation_id;
|
workingDirectoryId = payload.item.working_directory_id;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
submitError = exceptionDisplayError(err, 'working directory create failed');
|
submitError = exceptionDisplayError(err, 'working directory create failed');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -185,7 +185,7 @@
|
||||||
display_name: displayName,
|
display_name: displayName,
|
||||||
profile,
|
profile,
|
||||||
initial_text: initialText,
|
initial_text: initialText,
|
||||||
working_directory_allocation_id: workingDirectoryAllocationId,
|
working_directory_id: workingDirectoryId,
|
||||||
working_directory_repository_id: workingDirectoryRepositoryId,
|
working_directory_repository_id: workingDirectoryRepositoryId,
|
||||||
working_directory_selector: workingDirectorySelector,
|
working_directory_selector: workingDirectorySelector,
|
||||||
relative_cwd: relativeCwd,
|
relative_cwd: relativeCwd,
|
||||||
|
|
@ -279,18 +279,18 @@
|
||||||
<fieldset class="worker-working-directory">
|
<fieldset class="worker-working-directory">
|
||||||
<legend>Working directory</legend>
|
<legend>Working directory</legend>
|
||||||
<label>
|
<label>
|
||||||
<span>Allocation</span>
|
<span>Working directory</span>
|
||||||
<select bind:value={workingDirectoryAllocationId}>
|
<select bind:value={workingDirectoryId}>
|
||||||
<option value="">No allocation selected</option>
|
<option value="">No working directory selected</option>
|
||||||
{#each options?.working_directories ?? [] as directory}
|
{#each options?.working_directories ?? [] as directory}
|
||||||
<option value={directory.allocation_id} disabled={directory.status !== 'active'}>
|
<option value={directory.working_directory_id} disabled={directory.status !== 'active'}>
|
||||||
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'} · {directory.resolved_commit.slice(0, 12)} · {directory.status}
|
{directory.repository_id} · {directory.requested_selector ?? 'HEAD'} · {directory.resolved_commit.slice(0, 12)} · {directory.status}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span>Repository for new allocation</span>
|
<span>Repository</span>
|
||||||
<select bind:value={workingDirectoryRepositoryId}>
|
<select bind:value={workingDirectoryRepositoryId}>
|
||||||
{#if options?.repositories.length}
|
{#if options?.repositories.length}
|
||||||
{#each options.repositories as repository}
|
{#each options.repositories as repository}
|
||||||
|
|
@ -306,11 +306,11 @@
|
||||||
<input bind:value={workingDirectorySelector} autocomplete="off" placeholder="HEAD" />
|
<input bind:value={workingDirectorySelector} autocomplete="off" placeholder="HEAD" />
|
||||||
</label>
|
</label>
|
||||||
<button type="button" disabled={creatingWorkingDirectory || !workingDirectoryRepositoryId} onclick={() => void createWorkingDirectory()}>
|
<button type="button" disabled={creatingWorkingDirectory || !workingDirectoryRepositoryId} onclick={() => void createWorkingDirectory()}>
|
||||||
{creatingWorkingDirectory ? 'Allocating…' : 'Create working directory'}
|
{creatingWorkingDirectory ? 'Creating…' : 'Create working directory'}
|
||||||
</button>
|
</button>
|
||||||
<label>
|
<label>
|
||||||
<span>Relative cwd</span>
|
<span>Relative cwd</span>
|
||||||
<input bind:value={relativeCwd} autocomplete="off" placeholder="Optional path inside allocation" />
|
<input bind:value={relativeCwd} autocomplete="off" placeholder="Optional path inside working directory" />
|
||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<label>
|
<label>
|
||||||
|
|
@ -335,7 +335,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<button type="submit" disabled={submitting || !runtimeId || !profile || !workingDirectoryAllocationId}>
|
<button type="submit" disabled={submitting || !runtimeId || !profile || !workingDirectoryId}>
|
||||||
{submitting ? 'Starting…' : 'Start Coding Worker'}
|
{submitting ? 'Starting…' : 'Start Coding Worker'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export type WorkingDirectoryRepositoryOption = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkingDirectorySummary = {
|
export type WorkingDirectorySummary = {
|
||||||
allocation_id: string;
|
working_directory_id: string;
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
requested_selector?: string | null;
|
requested_selector?: string | null;
|
||||||
materializer_kind: string;
|
materializer_kind: string;
|
||||||
|
|
@ -127,7 +127,7 @@ export type WorkingDirectorySummary = {
|
||||||
cleanup_policy: string;
|
cleanup_policy: string;
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: string;
|
kind: string;
|
||||||
allocation_id: string;
|
working_directory_id: string;
|
||||||
repository_id: string;
|
repository_id: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -145,7 +145,7 @@ export type BrowserWorkingDirectoryListResponse = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BrowserWorkerWorkingDirectorySelection = {
|
export type BrowserWorkerWorkingDirectorySelection = {
|
||||||
allocation_id: string;
|
working_directory_id: string;
|
||||||
relative_cwd?: string | null;
|
relative_cwd?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||||
],
|
],
|
||||||
working_directories: [
|
working_directories: [
|
||||||
{
|
{
|
||||||
allocation_id: "alloc-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
repository_id: "repo",
|
repository_id: "repo",
|
||||||
requested_selector: "HEAD",
|
requested_selector: "HEAD",
|
||||||
materializer_kind: "local_git_worktree",
|
materializer_kind: "local_git_worktree",
|
||||||
|
|
@ -55,7 +55,7 @@ const options: WorkerLaunchOptionsResponse = {
|
||||||
cleanup_policy: "manual_or_worker_stop",
|
cleanup_policy: "manual_or_worker_stop",
|
||||||
cleanup_target: {
|
cleanup_target: {
|
||||||
kind: "git_worktree",
|
kind: "git_worktree",
|
||||||
allocation_id: "alloc-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
repository_id: "repo",
|
repository_id: "repo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -69,7 +69,7 @@ Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, reposi
|
||||||
display_name: "",
|
display_name: "",
|
||||||
profile: "",
|
profile: "",
|
||||||
initial_text: "hello",
|
initial_text: "hello",
|
||||||
working_directory_allocation_id: "",
|
working_directory_id: "",
|
||||||
working_directory_repository_id: "",
|
working_directory_repository_id: "",
|
||||||
working_directory_selector: "",
|
working_directory_selector: "",
|
||||||
relative_cwd: "",
|
relative_cwd: "",
|
||||||
|
|
@ -79,18 +79,18 @@ Deno.test("defaultWorkerLaunchForm chooses active runtime, coder profile, reposi
|
||||||
assertEquals(form.display_name, "Coding Worker");
|
assertEquals(form.display_name, "Coding Worker");
|
||||||
assertEquals(form.profile, "builtin:coder");
|
assertEquals(form.profile, "builtin:coder");
|
||||||
assertEquals(form.initial_text, "hello");
|
assertEquals(form.initial_text, "hello");
|
||||||
assertEquals(form.working_directory_allocation_id, "alloc-1-repo");
|
assertEquals(form.working_directory_id, "wd-1-repo");
|
||||||
assertEquals(form.working_directory_repository_id, "repo");
|
assertEquals(form.working_directory_repository_id, "repo");
|
||||||
assertEquals(form.working_directory_selector, "HEAD");
|
assertEquals(form.working_directory_selector, "HEAD");
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("buildBrowserCreateWorkerRequest sends allocation id and relative cwd only", () => {
|
Deno.test("buildBrowserCreateWorkerRequest sends working_directory id and relative cwd only", () => {
|
||||||
const request = buildBrowserCreateWorkerRequest({
|
const request = buildBrowserCreateWorkerRequest({
|
||||||
runtime_id: "embedded",
|
runtime_id: "embedded",
|
||||||
display_name: "Worker",
|
display_name: "Worker",
|
||||||
profile: "builtin:coder",
|
profile: "builtin:coder",
|
||||||
initial_text: "go",
|
initial_text: "go",
|
||||||
working_directory_allocation_id: "alloc-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
working_directory_repository_id: "repo",
|
working_directory_repository_id: "repo",
|
||||||
working_directory_selector: "main",
|
working_directory_selector: "main",
|
||||||
relative_cwd: "crates/yoi",
|
relative_cwd: "crates/yoi",
|
||||||
|
|
@ -102,7 +102,7 @@ Deno.test("buildBrowserCreateWorkerRequest sends allocation id and relative cwd
|
||||||
profile: "builtin:coder",
|
profile: "builtin:coder",
|
||||||
initial_text: "go",
|
initial_text: "go",
|
||||||
working_directory: {
|
working_directory: {
|
||||||
allocation_id: "alloc-1-repo",
|
working_directory_id: "wd-1-repo",
|
||||||
relative_cwd: "crates/yoi",
|
relative_cwd: "crates/yoi",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export type WorkerLaunchFormState = {
|
||||||
display_name: string;
|
display_name: string;
|
||||||
profile: string;
|
profile: string;
|
||||||
initial_text: string;
|
initial_text: string;
|
||||||
working_directory_allocation_id: string;
|
working_directory_id: string;
|
||||||
working_directory_repository_id: string;
|
working_directory_repository_id: string;
|
||||||
working_directory_selector: string;
|
working_directory_selector: string;
|
||||||
relative_cwd: string;
|
relative_cwd: string;
|
||||||
|
|
@ -54,12 +54,12 @@ export function defaultWorkerLaunchForm(
|
||||||
? current.profile
|
? current.profile
|
||||||
: preferredProfile?.id || "",
|
: preferredProfile?.id || "",
|
||||||
initial_text: current.initial_text,
|
initial_text: current.initial_text,
|
||||||
working_directory_allocation_id: options?.working_directories.some(
|
working_directory_id: options?.working_directories.some(
|
||||||
(directory) =>
|
(directory) =>
|
||||||
directory.allocation_id === current.working_directory_allocation_id,
|
directory.working_directory_id === current.working_directory_id,
|
||||||
)
|
)
|
||||||
? current.working_directory_allocation_id
|
? current.working_directory_id
|
||||||
: preferredWorkingDirectory?.allocation_id || "",
|
: preferredWorkingDirectory?.working_directory_id || "",
|
||||||
working_directory_repository_id: current.working_directory_repository_id ||
|
working_directory_repository_id: current.working_directory_repository_id ||
|
||||||
preferredRepository?.id || "",
|
preferredRepository?.id || "",
|
||||||
working_directory_selector: current.working_directory_selector ||
|
working_directory_selector: current.working_directory_selector ||
|
||||||
|
|
@ -77,9 +77,9 @@ export function buildBrowserCreateWorkerRequest(
|
||||||
profile: form.profile,
|
profile: form.profile,
|
||||||
initial_text: form.initial_text,
|
initial_text: form.initial_text,
|
||||||
};
|
};
|
||||||
if (form.working_directory_allocation_id) {
|
if (form.working_directory_id) {
|
||||||
request.working_directory = {
|
request.working_directory = {
|
||||||
allocation_id: form.working_directory_allocation_id,
|
working_directory_id: form.working_directory_id,
|
||||||
};
|
};
|
||||||
const relativeCwd = form.relative_cwd.trim();
|
const relativeCwd = form.relative_cwd.trim();
|
||||||
if (relativeCwd) {
|
if (relativeCwd) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user