feat: route ticket tools through workspace backend

This commit is contained in:
2026-07-16 01:18:13 +09:00
parent 66abd53e51
commit 983ffe6017
11 changed files with 1002 additions and 110 deletions
+23 -2
View File
@@ -17,7 +17,7 @@ use worker_runtime::catalog::{
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, ProfileSourceArchiveHttpRef,
ProfileSourceArchiveSource, WorkerDetail as EmbeddedWorkerDetail,
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest,
WorkingDirectoryStatus, WorkingDirectorySummary,
WorkingDirectoryStatus, WorkingDirectorySummary, WorkspaceApiRef,
};
use worker_runtime::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
#[cfg(test)]
@@ -1182,6 +1182,8 @@ impl RuntimeRegistry {
pub struct EmbeddedWorkerRuntime {
runtime_id: String,
host_id: String,
workspace_id: String,
backend_base_url: Option<String>,
runtime: worker_runtime::Runtime,
execution_enabled: bool,
resource_broker: BackendResourceBroker,
@@ -1234,10 +1236,18 @@ impl EmbeddedWorkerRuntime {
self
}
pub fn with_backend_base_url(mut self, backend_base_url: impl Into<String>) -> Self {
self.backend_base_url = Some(backend_base_url.into().trim_end_matches('/').to_string());
self
}
pub fn from_runtime(workspace_id: impl AsRef<str>, runtime: worker_runtime::Runtime) -> Self {
let workspace_id = workspace_id.as_ref().to_string();
Self {
runtime_id: EMBEDDED_RUNTIME_ID.to_string(),
host_id: host_id_for_embedded_workspace(workspace_id.as_ref()),
host_id: host_id_for_embedded_workspace(&workspace_id),
workspace_id,
backend_base_url: None,
runtime,
execution_enabled: false,
resource_broker: BackendResourceBroker::default(),
@@ -1556,6 +1566,13 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
initial_input: request.initial_input.clone(),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
workspace_api: self
.backend_base_url
.as_ref()
.map(|base_url| WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: base_url.clone(),
}),
};
match self.runtime.create_worker(create_request) {
Ok(detail) => {
@@ -2392,6 +2409,10 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
initial_input: request.initial_input.clone(),
working_directory_request: request.resolved_working_directory_request.clone(),
working_directory: request.resolved_working_directory.clone(),
workspace_api: Some(WorkspaceApiRef {
workspace_id: self.workspace_id.clone(),
base_url: self.backend_base_url.clone(),
}),
};
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
Ok(response) => WorkerSpawnResult {
+38 -1
View File
@@ -1,3 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::path::{Component, Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
@@ -13,7 +14,10 @@ use chrono::{SecondsFormat, Utc};
use futures::StreamExt;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{HashMap, HashSet};
use ticket::{
LocalTicketBackend, TicketBackendHttpResponse, TicketBackendOperation,
execute_ticket_backend_operation,
};
use tokio::net::TcpListener;
use worker_runtime::resource::{BackendResourceError, BackendResourceFetchRequest};
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
@@ -238,6 +242,14 @@ impl WorkspaceApi {
execution_backend,
)
.map(|runtime| runtime.with_resource_broker(resource_broker.clone()))
.map(|runtime| {
runtime.with_backend_base_url(
config
.backend_base_url
.clone()
.unwrap_or_else(|| "http://127.0.0.1:8787".to_string()),
)
})
.map_err(|err| {
crate::Error::Store(format!("invalid embedded Worker backend: {err}"))
})?,
@@ -313,6 +325,10 @@ pub fn build_router(api: WorkspaceApi) -> Router {
)
.route("/api/tickets", get(list_tickets))
.route("/api/w/{workspace_id}/tickets", get(scoped_list_tickets))
.route(
"/api/w/{workspace_id}/tickets/backend",
post(scoped_ticket_backend_operation),
)
.route("/api/tickets/{id}", get(get_ticket))
.route("/api/w/{workspace_id}/tickets/{id}", get(scoped_get_ticket))
.route("/api/objectives", get(list_objectives))
@@ -1226,6 +1242,26 @@ async fn scoped_get_ticket(
get_ticket(State(api), AxumPath(path.id)).await
}
async fn scoped_ticket_backend_operation(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
Json(operation): Json<TicketBackendOperation>,
) -> ApiResult<Json<TicketBackendHttpResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?;
let backend = LocalTicketBackend::new(
api.config
.workspace_root
.join(ticket::config::DEFAULT_TICKET_BACKEND_RELATIVE_PATH),
);
let response = match execute_ticket_backend_operation(&backend, operation) {
Ok(result) => TicketBackendHttpResponse::Ok { result },
Err(error) => TicketBackendHttpResponse::Error {
message: error.to_string(),
},
};
Ok(Json(response))
}
async fn scoped_list_objectives(
State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedWorkspacePath>,
@@ -6265,6 +6301,7 @@ mod tests {
initial_input: None,
working_directory_request: None,
working_directory: None,
workspace_api: None,
}
}