(
state: &Arc<ServerState>,
request: Request<CreateSandboxRequest>,
)
| 117 | } |
| 118 | |
| 119 | async fn handle_create_sandbox_inner( |
| 120 | state: &Arc<ServerState>, |
| 121 | request: Request<CreateSandboxRequest>, |
| 122 | ) -> Result<Response<SandboxResponse>, Status> { |
| 123 | let request = request.into_inner(); |
| 124 | let spec = request |
| 125 | .spec |
| 126 | .ok_or_else(|| Status::invalid_argument("spec is required"))?; |
| 127 | |
| 128 | // Validate field sizes before any I/O (fail fast on oversized payloads). |
| 129 | validate_sandbox_spec(&request.name, &spec)?; |
| 130 | |
| 131 | // Validate labels (keys and values must meet Kubernetes requirements). |
| 132 | for (key, value) in &request.labels { |
| 133 | crate::grpc::validation::validate_label_key(key)?; |
| 134 | crate::grpc::validation::validate_label_value(value)?; |
| 135 | } |
| 136 | |
| 137 | let _sandbox_sync_guard = if spec.providers.is_empty() { |
| 138 | None |
| 139 | } else { |
| 140 | Some(state.compute.sandbox_sync_guard().await) |
| 141 | }; |
| 142 | |
| 143 | // Validate provider names exist (fail fast). |
| 144 | for name in &spec.providers { |
| 145 | state |
| 146 | .store |
| 147 | .get_message_by_name::<Provider>(name) |
| 148 | .await |
| 149 | .map_err(|e| Status::internal(format!("fetch provider failed: {e}")))? |
| 150 | .ok_or_else(|| Status::failed_precondition(format!("provider '{name}' not found")))?; |
| 151 | } |
| 152 | validate_provider_environment_keys_unique(state.store.as_ref(), &spec.providers).await?; |
| 153 | |
| 154 | // Ensure the template always carries the resolved image. |
| 155 | let mut spec = spec; |
| 156 | let template = spec.template.get_or_insert_with(SandboxTemplate::default); |
| 157 | if template.image.is_empty() { |
| 158 | template.image = state.compute.default_image().to_string(); |
| 159 | } |
| 160 | |
| 161 | // Ensure process identity defaults to "sandbox" when missing or |
| 162 | // empty, then validate policy safety before persisting. |
| 163 | if let Some(ref mut policy) = spec.policy { |
| 164 | openshell_policy::ensure_sandbox_process_identity(policy); |
| 165 | validate_no_reserved_provider_policy_keys(policy)?; |
| 166 | validate_policy_safety(policy)?; |
| 167 | } |
| 168 | |
| 169 | let id = uuid::Uuid::new_v4().to_string(); |
| 170 | let name = if request.name.is_empty() { |
| 171 | petname::petname(2, "-").unwrap_or_else(generate_name) |
| 172 | } else { |
| 173 | request.name.clone() |
| 174 | }; |
| 175 | |
| 176 | let now_ms = current_time_ms(); |
no test coverage detected