feat: add runtime config bundle sync
This commit is contained in:
@@ -16,11 +16,13 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use worker_runtime::catalog::{
|
||||
CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail, WorkerIntent,
|
||||
WorkerStatus as EmbeddedWorkerStatus,
|
||||
CapabilityRequest, ConfigBundleRef, CreateWorkerRequest, ProfileSelector,
|
||||
WorkerDetail as EmbeddedWorkerDetail, WorkerIntent, WorkerStatus as EmbeddedWorkerStatus,
|
||||
};
|
||||
use worker_runtime::config_bundle::{ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary};
|
||||
use worker_runtime::error::RuntimeError as EmbeddedRuntimeError;
|
||||
use worker_runtime::http_server::{
|
||||
RuntimeHttpConfigBundleAvailabilityResponse, RuntimeHttpConfigBundleSyncRequest,
|
||||
RuntimeHttpErrorResponse, RuntimeHttpSummaryResponse, RuntimeHttpTranscriptResponse,
|
||||
RuntimeHttpWorkerInputResponse, RuntimeHttpWorkerLifecycleRequest,
|
||||
RuntimeHttpWorkerLifecycleResponse, RuntimeHttpWorkerResponse, RuntimeHttpWorkersResponse,
|
||||
@@ -261,16 +263,24 @@ pub struct WorkerLookupResult {
|
||||
|
||||
/// Browser-safe worker spawn request shape.
|
||||
///
|
||||
/// The request intentionally carries only workspace policy intents and stable
|
||||
/// worker identifiers. Raw workspace roots, child cwd, executable path, and raw
|
||||
/// profile selectors are resolved by the runtime service and never accepted from
|
||||
/// Workspace API callers.
|
||||
/// The request intentionally carries only workspace policy intents, stable
|
||||
/// worker identifiers, optional profile selectors, config bundle refs, and
|
||||
/// requested capability names. Raw workspace roots, child cwd, executable path,
|
||||
/// Runtime endpoints/credentials, raw bundle storage paths, and host-local
|
||||
/// resolved WorkerSpec content are resolved by the runtime service and never
|
||||
/// accepted from Workspace API callers.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct WorkerSpawnRequest {
|
||||
pub intent: WorkerSpawnIntent,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub requested_worker_name: Option<String>,
|
||||
pub acceptance: WorkerSpawnAcceptanceRequirement,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<ProfileSelector>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub config_bundle: Option<ConfigBundleRef>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub requested_capabilities: Vec<CapabilityRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -309,6 +319,28 @@ pub struct WorkerSpawnResult {
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ConfigBundleSyncResult {
|
||||
pub state: WorkerOperationState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub availability: Option<ConfigBundleAvailability>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ConfigBundleCheckResult {
|
||||
pub state: WorkerOperationState,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub availability: Option<ConfigBundleAvailability>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct ConfigBundleListResult {
|
||||
pub bundles: Vec<ConfigBundleSummary>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorkerOperationState {
|
||||
@@ -492,6 +524,41 @@ pub trait WorkspaceWorkerRuntime: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_config_bundle(&self, _bundle: ConfigBundle) -> ConfigBundleSyncResult {
|
||||
ConfigBundleSyncResult {
|
||||
state: WorkerOperationState::Unsupported,
|
||||
availability: None,
|
||||
diagnostics: vec![diagnostic(
|
||||
"config_bundle_sync_unsupported",
|
||||
DiagnosticSeverity::Info,
|
||||
"runtime does not implement config bundle sync".to_string(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config_bundle(&self, _reference: ConfigBundleRef) -> ConfigBundleCheckResult {
|
||||
ConfigBundleCheckResult {
|
||||
state: WorkerOperationState::Unsupported,
|
||||
availability: None,
|
||||
diagnostics: vec![diagnostic(
|
||||
"config_bundle_check_unsupported",
|
||||
DiagnosticSeverity::Info,
|
||||
"runtime does not implement config bundle availability checks".to_string(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn list_config_bundles(&self) -> ConfigBundleListResult {
|
||||
ConfigBundleListResult {
|
||||
bundles: Vec::new(),
|
||||
diagnostics: vec![diagnostic(
|
||||
"config_bundle_list_unsupported",
|
||||
DiagnosticSeverity::Info,
|
||||
"runtime does not implement config bundle listing".to_string(),
|
||||
)],
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_worker(
|
||||
&self,
|
||||
worker_id: &str,
|
||||
@@ -729,6 +796,35 @@ impl RuntimeRegistry {
|
||||
Ok(runtime.spawn_worker(request))
|
||||
}
|
||||
|
||||
pub fn sync_config_bundle(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
bundle: ConfigBundle,
|
||||
) -> Result<ConfigBundleSyncResult, RuntimeRegistryError> {
|
||||
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||
let runtime = self.runtime(runtime_id)?;
|
||||
Ok(runtime.sync_config_bundle(bundle))
|
||||
}
|
||||
|
||||
pub fn check_config_bundle(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
reference: ConfigBundleRef,
|
||||
) -> Result<ConfigBundleCheckResult, RuntimeRegistryError> {
|
||||
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||
let runtime = self.runtime(runtime_id)?;
|
||||
Ok(runtime.check_config_bundle(reference))
|
||||
}
|
||||
|
||||
pub fn list_config_bundles(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
) -> Result<ConfigBundleListResult, RuntimeRegistryError> {
|
||||
validate_backend_identifier("runtime_id", runtime_id)?;
|
||||
let runtime = self.runtime(runtime_id)?;
|
||||
Ok(runtime.list_config_bundles())
|
||||
}
|
||||
|
||||
pub fn send_input(
|
||||
&self,
|
||||
runtime_id: &str,
|
||||
@@ -1091,10 +1187,21 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||
));
|
||||
}
|
||||
|
||||
let create_request = CreateWorkerRequest::tools_less(
|
||||
embedded_create_intent(&request.intent),
|
||||
embedded_profile_selector(&request.intent),
|
||||
);
|
||||
let create_request = CreateWorkerRequest {
|
||||
intent: embedded_create_intent(&request.intent),
|
||||
profile: request
|
||||
.profile
|
||||
.clone()
|
||||
.unwrap_or_else(|| embedded_profile_selector(&request.intent)),
|
||||
config_bundle: request.config_bundle.clone(),
|
||||
requested_capabilities: if request.requested_capabilities.is_empty() {
|
||||
vec![CapabilityRequest::named("read")]
|
||||
} else {
|
||||
request.requested_capabilities.clone()
|
||||
},
|
||||
workspace_refs: Vec::new(),
|
||||
mount_refs: Vec::new(),
|
||||
};
|
||||
match self.runtime.create_worker(create_request) {
|
||||
Ok(detail) => WorkerSpawnResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
@@ -1126,6 +1233,49 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_config_bundle(&self, bundle: ConfigBundle) -> ConfigBundleSyncResult {
|
||||
match self.runtime.store_config_bundle(bundle) {
|
||||
Ok(availability) => ConfigBundleSyncResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
availability: Some(availability),
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(error) => ConfigBundleSyncResult {
|
||||
state: WorkerOperationState::Rejected,
|
||||
availability: None,
|
||||
diagnostics: vec![embedded_runtime_diagnostic(&error)],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config_bundle(&self, reference: ConfigBundleRef) -> ConfigBundleCheckResult {
|
||||
match self.runtime.check_config_bundle(&reference) {
|
||||
Ok(availability) => ConfigBundleCheckResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
availability: Some(availability),
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(error) => ConfigBundleCheckResult {
|
||||
state: WorkerOperationState::Rejected,
|
||||
availability: None,
|
||||
diagnostics: vec![embedded_runtime_diagnostic(&error)],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn list_config_bundles(&self) -> ConfigBundleListResult {
|
||||
match self.runtime.list_config_bundles() {
|
||||
Ok(bundles) => ConfigBundleListResult {
|
||||
bundles,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(error) => ConfigBundleListResult {
|
||||
bundles: Vec::new(),
|
||||
diagnostics: vec![embedded_runtime_diagnostic(&error)],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn send_input(&self, worker_id: &str, request: WorkerInputRequest) -> WorkerInputResult {
|
||||
let Some(worker_ref) = self.worker_ref(worker_id) else {
|
||||
return embedded_input_rejected(
|
||||
@@ -1574,10 +1724,21 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||
)],
|
||||
};
|
||||
}
|
||||
let create = CreateWorkerRequest::tools_less(
|
||||
embedded_create_intent(&request.intent),
|
||||
embedded_profile_selector(&request.intent),
|
||||
);
|
||||
let create = CreateWorkerRequest {
|
||||
intent: embedded_create_intent(&request.intent),
|
||||
profile: request
|
||||
.profile
|
||||
.clone()
|
||||
.unwrap_or_else(|| embedded_profile_selector(&request.intent)),
|
||||
config_bundle: request.config_bundle.clone(),
|
||||
requested_capabilities: if request.requested_capabilities.is_empty() {
|
||||
vec![CapabilityRequest::named("read")]
|
||||
} else {
|
||||
request.requested_capabilities.clone()
|
||||
},
|
||||
workspace_refs: Vec::new(),
|
||||
mount_refs: Vec::new(),
|
||||
};
|
||||
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
||||
Ok(response) => WorkerSpawnResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
@@ -1601,6 +1762,44 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_config_bundle(&self, bundle: ConfigBundle) -> ConfigBundleSyncResult {
|
||||
let request = RuntimeHttpConfigBundleSyncRequest { bundle };
|
||||
match self.post_json::<_, RuntimeHttpConfigBundleAvailabilityResponse>(
|
||||
"/v1/config-bundles",
|
||||
&request,
|
||||
) {
|
||||
Ok(response) => ConfigBundleSyncResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
availability: Some(response.availability),
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(diagnostic) => ConfigBundleSyncResult {
|
||||
state: WorkerOperationState::Rejected,
|
||||
availability: None,
|
||||
diagnostics: vec![diagnostic],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn check_config_bundle(&self, reference: ConfigBundleRef) -> ConfigBundleCheckResult {
|
||||
let path = format!(
|
||||
"/v1/config-bundles/{}/availability?digest={}",
|
||||
reference.id, reference.digest
|
||||
);
|
||||
match self.get_json::<RuntimeHttpConfigBundleAvailabilityResponse>(&path) {
|
||||
Ok(response) => ConfigBundleCheckResult {
|
||||
state: WorkerOperationState::Accepted,
|
||||
availability: Some(response.availability),
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
Err(diagnostic) => ConfigBundleCheckResult {
|
||||
state: WorkerOperationState::Rejected,
|
||||
availability: None,
|
||||
diagnostics: vec![diagnostic],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_worker(
|
||||
&self,
|
||||
worker_id: &str,
|
||||
@@ -2197,7 +2396,11 @@ fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnosti
|
||||
DiagnosticSeverity::Warning,
|
||||
format!("Requested limit {requested} exceeds embedded Runtime maximum {max}"),
|
||||
),
|
||||
EmbeddedRuntimeError::InvalidRequest(_) => diagnostic(
|
||||
EmbeddedRuntimeError::InvalidRequest(_)
|
||||
| EmbeddedRuntimeError::ConfigBundleMissing { .. }
|
||||
| EmbeddedRuntimeError::ConfigBundleDigestMismatch { .. }
|
||||
| EmbeddedRuntimeError::InvalidProfileSelector { .. }
|
||||
| EmbeddedRuntimeError::UnsupportedConfigDeclaration { .. } => diagnostic(
|
||||
"embedded_runtime_invalid_request",
|
||||
DiagnosticSeverity::Warning,
|
||||
"Embedded Runtime rejected the request".to_string(),
|
||||
@@ -2592,6 +2795,32 @@ mod tests {
|
||||
metadata
|
||||
}
|
||||
|
||||
fn test_config_bundle() -> ConfigBundle {
|
||||
ConfigBundle {
|
||||
metadata: worker_runtime::config_bundle::ConfigBundleMetadata {
|
||||
id: "bundle-1".to_string(),
|
||||
digest: String::new(),
|
||||
revision: "rev-1".to_string(),
|
||||
workspace_id: "local:test".to_string(),
|
||||
created_at: "2026-06-26T00:00:00Z".to_string(),
|
||||
provenance: worker_runtime::config_bundle::ConfigBundleProvenance {
|
||||
source: "workspace-server-test".to_string(),
|
||||
detail: None,
|
||||
},
|
||||
},
|
||||
profiles: vec![worker_runtime::config_bundle::ConfigProfileDescriptor {
|
||||
selector: ProfileSelector::Builtin("builtin:coder".to_string()),
|
||||
label: Some("Coder".to_string()),
|
||||
}],
|
||||
declarations: vec![worker_runtime::config_bundle::ConfigDeclaration {
|
||||
kind: worker_runtime::config_bundle::ConfigDeclarationKind::CapabilityGrant,
|
||||
name: "read".to_string(),
|
||||
reference: "capability:read".to_string(),
|
||||
}],
|
||||
}
|
||||
.with_computed_digest()
|
||||
}
|
||||
|
||||
fn assert_valid_generated_id(id: &str) {
|
||||
assert!(id.len() <= MAX_IDENTIFIER_LEN, "id too long: {id}");
|
||||
validate_backend_identifier("test_id", id).unwrap();
|
||||
@@ -2941,6 +3170,9 @@ mod tests {
|
||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||
expected_segments: 0,
|
||||
},
|
||||
profile: None,
|
||||
config_bundle: None,
|
||||
requested_capabilities: Vec::new(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3013,6 +3245,50 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_backend_syncs_config_bundle_and_spawns_with_bundle_ref() {
|
||||
let registry = RuntimeRegistry::new(vec![Arc::new(EmbeddedWorkerRuntime::new_memory(
|
||||
"local:test",
|
||||
))]);
|
||||
let bundle = test_config_bundle();
|
||||
let sync = registry
|
||||
.sync_config_bundle(EMBEDDED_RUNTIME_ID, bundle.clone())
|
||||
.unwrap();
|
||||
assert_eq!(sync.state, WorkerOperationState::Accepted);
|
||||
let reference = sync.availability.expect("bundle availability").reference;
|
||||
assert_eq!(reference.id, bundle.metadata.id);
|
||||
assert_eq!(reference.digest, bundle.metadata.digest);
|
||||
|
||||
let check = registry
|
||||
.check_config_bundle(EMBEDDED_RUNTIME_ID, reference.clone())
|
||||
.unwrap();
|
||||
assert_eq!(check.state, WorkerOperationState::Accepted);
|
||||
|
||||
let spawned = registry
|
||||
.spawn_worker(
|
||||
EMBEDDED_RUNTIME_ID,
|
||||
WorkerSpawnRequest {
|
||||
intent: WorkerSpawnIntent::TicketRole {
|
||||
ticket_id: "00001KVZSGT0Q".to_string(),
|
||||
role: TicketWorkerRole::Coder,
|
||||
},
|
||||
requested_worker_name: None,
|
||||
acceptance: WorkerSpawnAcceptanceRequirement::RunAccepted {
|
||||
expected_segments: 0,
|
||||
},
|
||||
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
||||
config_bundle: Some(reference),
|
||||
requested_capabilities: vec![CapabilityRequest::named("read")],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(spawned.state, WorkerOperationState::Accepted);
|
||||
assert_eq!(
|
||||
spawned.worker.unwrap().profile.as_deref(),
|
||||
Some("builtin:coder")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embedded_runtime_rejects_socket_ready_acceptance_without_socket_identity() {
|
||||
let registry = RuntimeRegistry::new(vec![Arc::new(EmbeddedWorkerRuntime::new_memory(
|
||||
@@ -3025,6 +3301,9 @@ mod tests {
|
||||
intent: WorkerSpawnIntent::WorkspaceCompanion,
|
||||
requested_worker_name: None,
|
||||
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
||||
profile: None,
|
||||
config_bundle: None,
|
||||
requested_capabilities: Vec::new(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -13,10 +13,11 @@ use serde::{Deserialize, Serialize};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::hosts::{
|
||||
DiagnosticSeverity, EmbeddedWorkerRuntime, HostSummary, LocalWorkerRuntime,
|
||||
RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry, RuntimeSummary,
|
||||
WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest, WorkerLifecycleResult,
|
||||
WorkerSpawnRequest, WorkerSpawnResult, WorkerSummary, WorkerTranscriptProjection,
|
||||
ConfigBundleCheckResult, ConfigBundleSyncResult, DiagnosticSeverity, EmbeddedWorkerRuntime,
|
||||
HostSummary, LocalWorkerRuntime, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic,
|
||||
RuntimeRegistry, RuntimeSummary, WorkerInputRequest, WorkerInputResult, WorkerLifecycleRequest,
|
||||
WorkerLifecycleResult, WorkerSpawnRequest, WorkerSpawnResult, WorkerSummary,
|
||||
WorkerTranscriptProjection,
|
||||
};
|
||||
use crate::identity::WorkspaceIdentity;
|
||||
use crate::observation::{
|
||||
@@ -29,6 +30,8 @@ use crate::records::{
|
||||
use crate::repositories::{LocalRepositoryReader, RepositoryLogRead, RepositorySummary};
|
||||
use crate::store::{ControlPlaneStore, WorkspaceRecord};
|
||||
use crate::{Error, Result};
|
||||
use worker_runtime::catalog::ConfigBundleRef;
|
||||
use worker_runtime::config_bundle::ConfigBundle;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum AuthConfig {
|
||||
@@ -155,6 +158,14 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||
"/api/runtimes/{runtime_id}/workers",
|
||||
post(create_runtime_worker),
|
||||
)
|
||||
.route(
|
||||
"/api/runtimes/{runtime_id}/config-bundles",
|
||||
post(sync_runtime_config_bundle),
|
||||
)
|
||||
.route(
|
||||
"/api/runtimes/{runtime_id}/config-bundles/{bundle_id}/availability",
|
||||
get(check_runtime_config_bundle),
|
||||
)
|
||||
.route(
|
||||
"/api/runtimes/{runtime_id}/workers/{worker_id}",
|
||||
get(get_runtime_worker),
|
||||
@@ -491,6 +502,16 @@ async fn get_runtime_worker(
|
||||
Ok(Json(worker))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RuntimeConfigBundleSyncRequest {
|
||||
pub bundle: ConfigBundle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct RuntimeConfigBundleAvailabilityQuery {
|
||||
digest: String,
|
||||
}
|
||||
|
||||
async fn create_runtime_worker(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(runtime_id): AxumPath<String>,
|
||||
@@ -503,6 +524,36 @@ async fn create_runtime_worker(
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn sync_runtime_config_bundle(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(runtime_id): AxumPath<String>,
|
||||
Json(request): Json<RuntimeConfigBundleSyncRequest>,
|
||||
) -> ApiResult<Json<ConfigBundleSyncResult>> {
|
||||
let result = api
|
||||
.runtime
|
||||
.sync_config_bundle(&runtime_id, request.bundle)
|
||||
.map_err(|err| err.into_error())?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn check_runtime_config_bundle(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath((runtime_id, bundle_id)): AxumPath<(String, String)>,
|
||||
Query(query): Query<RuntimeConfigBundleAvailabilityQuery>,
|
||||
) -> ApiResult<Json<ConfigBundleCheckResult>> {
|
||||
let result = api
|
||||
.runtime
|
||||
.check_config_bundle(
|
||||
&runtime_id,
|
||||
ConfigBundleRef {
|
||||
id: bundle_id,
|
||||
digest: query.digest,
|
||||
},
|
||||
)
|
||||
.map_err(|err| err.into_error())?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
async fn send_runtime_worker_input(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath((runtime_id, worker_id)): AxumPath<(String, String)>,
|
||||
|
||||
Reference in New Issue
Block a user