1use std::collections::BTreeMap;
14use std::path::PathBuf;
15
16use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18
19use crate::domain::{
20 CpuPlacement, DeploymentProfile, EnvVar, HandoffInit, NetworkSpec, OciRootfsSource, RootDisk,
21 RootfsSource, SandboxPolicy, SandboxResources, SandboxRuntimeOptions, SandboxSpec,
22 SecurityProfile, TransparentHugePagePolicy, VsockSpec,
23};
24use crate::{TypesError, TypesResult};
25
26mod secrets;
27mod snapshots;
28mod specs;
29
30pub use secrets::{
31 CloudHostPattern, CloudSecretEntry, CloudSecretSource, CloudSecretsConfig, CloudViolationAction,
32};
33pub use snapshots::{
34 CloudCreateSnapshotRequest, CloudSnapshot, CloudSnapshotDetails, CloudSnapshotKind,
35 CloudSnapshotLocation, CloudSnapshotOperation, CloudSnapshotOperationStatus, CloudSnapshotSpec,
36};
37pub use specs::{
38 CloudDiskImageFormat, CloudNetworkSpec, CloudPatch, CloudPullPolicy, CloudRlimit,
39 CloudRlimitResource, CloudRootfsSource, CloudSandboxRuntimeOptions, CloudVolumeMount,
40};
41
42#[derive(Debug, Clone, Serialize)]
52#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
53#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
54#[serde(tag = "source", rename_all = "snake_case")]
55pub enum CloudCreateSandboxRequest {
56 Oci {
58 #[serde(flatten)]
60 sandbox: CloudSandboxSpec,
61 reference: String,
63 #[serde(default)]
65 resources: CloudSandboxResources,
66 #[serde(default)]
68 patches: Vec<CloudPatch>,
69 #[serde(default)]
71 pull_policy: CloudPullPolicy,
72 },
73 Bind {
75 #[serde(flatten)]
77 sandbox: CloudSandboxSpec,
78 #[cfg_attr(feature = "ts", ts(type = "string"))]
80 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
81 path: PathBuf,
82 #[serde(default)]
84 resources: CloudSandboxComputeResources,
85 #[serde(default)]
87 patches: Vec<CloudPatch>,
88 },
89 DiskImage {
91 #[serde(flatten)]
93 sandbox: CloudSandboxSpec,
94 #[cfg_attr(feature = "ts", ts(type = "string"))]
96 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
97 path: PathBuf,
98 format: CloudDiskImageFormat,
100 fstype: Option<String>,
102 #[serde(default)]
104 resources: CloudSandboxComputeResources,
105 #[serde(default)]
107 patches: Vec<CloudPatch>,
108 },
109 DiskSnapshot {
111 #[serde(flatten)]
113 sandbox: CloudSandboxSpec,
114 disk_snapshot_ref: CloudSnapshotLocation,
116 #[serde(default)]
118 resources: CloudSandboxComputeResources,
119 #[serde(default)]
121 pull_policy: CloudPullPolicy,
122 },
123}
124
125#[derive(Debug, Clone, Default, Serialize, Deserialize)]
127#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
128#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
129#[serde(default)]
130pub struct CloudSandboxSpec {
131 #[cfg_attr(feature = "utoipa", schema(required = true))]
133 pub name: String,
134
135 pub runtime: CloudSandboxRuntimeOptions,
137
138 pub env: Vec<EnvVar>,
140
141 pub labels: BTreeMap<String, String>,
143
144 pub rlimits: Vec<CloudRlimit>,
146
147 pub mounts: Vec<CloudVolumeMount>,
149
150 pub network: CloudNetworkSpec,
152
153 pub init: Option<HandoffInit>,
155
156 pub security_profile: SecurityProfile,
158
159 pub lifecycle: SandboxPolicy,
161}
162
163#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
165#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
166#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
167#[serde(default)]
168pub struct CloudSandboxComputeResources {
169 pub vcpus: u8,
171
172 pub memory_mib: u32,
174}
175
176#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
178#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
179#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
180#[serde(default)]
181pub struct CloudSandboxResources {
182 pub vcpus: u8,
184
185 pub memory_mib: u32,
187
188 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub disk_size_mib: Option<u32>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
199#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
200#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
201pub struct CloudCreateSandboxResponse {
202 pub id: String,
204 pub org_id: String,
206 pub name: String,
208 pub slug: String,
210 pub status: CloudSandboxStatus,
212 #[serde(default)]
215 pub status_reason: Option<CloudSandboxStatusReason>,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
220 #[cfg_attr(feature = "ts", ts(type = "unknown | null | undefined"))]
221 pub spec: Option<serde_json::Value>,
222 pub ephemeral: bool,
224 #[cfg_attr(feature = "ts", ts(type = "string"))]
226 pub created_at: DateTime<Utc>,
227 #[serde(default)]
229 #[cfg_attr(feature = "ts", ts(type = "string | null"))]
230 pub started_at: Option<DateTime<Utc>>,
231 #[serde(default)]
233 #[cfg_attr(feature = "ts", ts(type = "string | null"))]
234 pub stopped_at: Option<DateTime<Utc>>,
235 #[serde(default)]
237 pub last_failure_message: Option<String>,
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
242#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
243#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
244#[serde(rename_all = "snake_case")]
245pub enum CloudSandboxStatus {
246 Created,
248 Starting,
250 Running,
252 Stopping,
254 Stopped,
256 Failed,
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
263#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
264#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
265#[serde(rename_all = "snake_case")]
266pub enum CloudSandboxStatusReason {
267 Scheduling,
269 InsufficientCapacity,
272}
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
277#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
278pub struct CloudPaginated<T> {
279 pub data: Vec<T>,
281 #[serde(default)]
283 pub next_cursor: Option<String>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
289#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
290pub struct CloudMessageResponse {
291 pub message: String,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
298#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
299pub struct CloudErrorBody {
300 #[serde(default)]
302 pub code: Option<String>,
303 #[serde(default)]
305 pub message: Option<String>,
306 #[serde(default)]
308 pub error: Option<CloudErrorDetails>,
309}
310
311#[derive(Debug, Clone, Serialize, Deserialize)]
313#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
314#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
315pub struct CloudErrorDetails {
316 #[serde(default)]
318 pub code: Option<String>,
319 #[serde(default)]
321 pub message: Option<String>,
322}
323
324impl TryFrom<CloudCreateSandboxRequest> for SandboxSpec {
329 type Error = TypesError;
330
331 fn try_from(req: CloudCreateSandboxRequest) -> TypesResult<Self> {
332 match req {
333 CloudCreateSandboxRequest::Oci {
334 sandbox,
335 reference,
336 resources,
337 patches,
338 pull_policy,
339 } => sandbox.into_domain_spec(
340 RootfsSource::Oci(OciRootfsSource {
341 reference,
342 root_disk: resources.disk_size_mib.map(RootDisk::managed),
343 }),
344 resources.into(),
345 patches,
346 pull_policy,
347 ),
348 CloudCreateSandboxRequest::Bind {
349 sandbox,
350 path,
351 resources,
352 patches,
353 } => sandbox.into_domain_spec(
354 RootfsSource::Bind {
355 path,
356 follow_root_symlinks: false,
357 },
358 resources,
359 patches,
360 CloudPullPolicy::default(),
361 ),
362 CloudCreateSandboxRequest::DiskImage {
363 sandbox,
364 path,
365 format,
366 fstype,
367 resources,
368 patches,
369 } => sandbox.into_domain_spec(
370 RootfsSource::DiskImage {
371 path,
372 format: format.into(),
373 fstype,
374 },
375 resources,
376 patches,
377 CloudPullPolicy::default(),
378 ),
379 CloudCreateSandboxRequest::DiskSnapshot { .. } => Err(TypesError::invalid_config(
380 "disk_snapshot_ref is not supported here: resolve the snapshot reference \
381 to a concrete image before converting to a sandbox spec",
382 )),
383 }
384 }
385}
386
387impl CloudCreateSandboxRequest {
388 pub const fn sandbox_spec(&self) -> &CloudSandboxSpec {
390 match self {
391 Self::Oci { sandbox, .. }
392 | Self::Bind { sandbox, .. }
393 | Self::DiskImage { sandbox, .. }
394 | Self::DiskSnapshot { sandbox, .. } => sandbox,
395 }
396 }
397
398 pub const fn sandbox_spec_mut(&mut self) -> &mut CloudSandboxSpec {
400 match self {
401 Self::Oci { sandbox, .. }
402 | Self::Bind { sandbox, .. }
403 | Self::DiskImage { sandbox, .. }
404 | Self::DiskSnapshot { sandbox, .. } => sandbox,
405 }
406 }
407
408 pub const fn disk_snapshot_ref(&self) -> Option<&CloudSnapshotLocation> {
410 match self {
411 Self::DiskSnapshot {
412 disk_snapshot_ref, ..
413 } => Some(disk_snapshot_ref),
414 _ => None,
415 }
416 }
417
418 pub fn oci_reference(&self) -> Option<&str> {
420 match self {
421 Self::Oci { reference, .. } => Some(reference),
422 _ => None,
423 }
424 }
425
426 pub const fn compute_resources(&self) -> CloudSandboxComputeResources {
428 match self {
429 Self::Oci { resources, .. } => CloudSandboxComputeResources {
430 vcpus: resources.vcpus,
431 memory_mib: resources.memory_mib,
432 },
433 Self::Bind { resources, .. }
434 | Self::DiskImage { resources, .. }
435 | Self::DiskSnapshot { resources, .. } => *resources,
436 }
437 }
438
439 pub const fn oci_disk_size_mib(&self) -> Option<Option<u32>> {
441 match self {
442 Self::Oci { resources, .. } => Some(resources.disk_size_mib),
443 _ => None,
444 }
445 }
446
447 pub fn set_oci_disk_size_mib(&mut self, disk_size_mib: u32) -> bool {
449 let Self::Oci { resources, .. } = self else {
450 return false;
451 };
452 resources.disk_size_mib = Some(disk_size_mib);
453 true
454 }
455}
456
457impl CloudSandboxSpec {
458 fn into_domain_spec(
459 self,
460 image: RootfsSource,
461 resources: CloudSandboxComputeResources,
462 patches: Vec<CloudPatch>,
463 pull_policy: CloudPullPolicy,
464 ) -> TypesResult<SandboxSpec> {
465 let resources = SandboxResources {
466 cpus: resources.vcpus,
467 memory_mib: resources.memory_mib,
468 max_cpus: resources.vcpus,
472 max_memory_mib: resources.memory_mib,
473 cpu_placement: CpuPlacement::Inherit,
477 placement_profile: None,
478 thp: TransparentHugePagePolicy::Madvise,
479 };
480
481 let network = NetworkSpec {
485 enabled: self.network.enabled,
486 interface: None,
487 ports: Vec::new(),
488 policy: self.network.policy,
489 dns: None,
490 tls: None,
491 strict: self.network.strict,
492 secrets: self.network.secrets.map(Into::into),
493 max_tcp_connections: self.network.max_tcp_connections,
494 max_udp_connections: self.network.max_udp_connections,
495 tcp_accept_queue_size: None,
497 rate_limiter: None,
498 nat64_prefixes: NetworkSpec::default().nat64_prefixes,
499 trust_host_cas: false,
500 http: Default::default(),
501 outbound_proxy: None,
502 };
503 let runtime = SandboxRuntimeOptions {
504 workdir: self.runtime.workdir,
505 shell: self.runtime.shell,
506 scripts: self.runtime.scripts,
507 entrypoint: self.runtime.entrypoint,
508 cmd: self.runtime.cmd,
509 hostname: None,
510 user: self.runtime.user,
511 log_level: self.runtime.log_level,
512 metrics_sample_interval_ms: None,
513 disable_metrics_sample: false,
514 guest_clock: None,
515 };
516
517 Ok(SandboxSpec {
518 name: self.name,
519 image,
520 resources,
521 runtime,
522 env: self.env,
523 labels: self.labels,
524 rlimits: self.rlimits.into_iter().map(Into::into).collect(),
525 mounts: self.mounts.into_iter().map(Into::into).collect(),
526 patches: patches.into_iter().map(Into::into).collect(),
527 network,
528 vsock: VsockSpec::default(),
529 init: self.init,
530 pull_policy: pull_policy.into(),
531 security_profile: self.security_profile,
532 deployment_profile: DeploymentProfile::default(),
533 lifecycle: self.lifecycle,
534 })
535 }
536}
537
538impl From<SandboxSpec> for CloudCreateSandboxRequest {
539 fn from(spec: SandboxSpec) -> Self {
540 let resources = CloudSandboxComputeResources {
541 vcpus: spec.resources.cpus,
542 memory_mib: spec.resources.memory_mib,
543 };
544 let patches = spec.patches.into_iter().map(Into::into).collect();
545 let pull_policy = spec.pull_policy.into();
546 let sandbox = CloudSandboxSpec {
547 name: spec.name,
548 runtime: CloudSandboxRuntimeOptions {
549 workdir: spec.runtime.workdir,
550 shell: spec.runtime.shell,
551 scripts: spec.runtime.scripts,
552 entrypoint: spec.runtime.entrypoint,
553 cmd: spec.runtime.cmd,
554 user: spec.runtime.user,
555 log_level: spec.runtime.log_level,
556 },
557 env: spec.env,
558 labels: spec.labels,
559 rlimits: spec.rlimits.into_iter().map(Into::into).collect(),
560 mounts: spec.mounts.into_iter().map(Into::into).collect(),
561 network: CloudNetworkSpec {
562 enabled: spec.network.enabled,
563 policy: spec.network.policy,
564 secrets: spec.network.secrets.map(Into::into),
565 strict: spec.network.strict,
566 max_tcp_connections: spec.network.max_tcp_connections,
567 max_udp_connections: spec.network.max_udp_connections,
568 },
569 init: spec.init,
570 security_profile: spec.security_profile,
571 lifecycle: spec.lifecycle,
572 };
573
574 match spec.image {
575 RootfsSource::Oci(oci) => Self::Oci {
576 sandbox,
577 reference: oci.reference,
578 resources: CloudSandboxResources {
579 vcpus: resources.vcpus,
580 memory_mib: resources.memory_mib,
581 disk_size_mib: match oci.root_disk {
582 Some(RootDisk::Managed { size_mib }) => size_mib,
583 _ => None,
584 },
585 },
586 patches,
587 pull_policy,
588 },
589 RootfsSource::Bind { path, .. } => Self::Bind {
590 sandbox,
591 path,
592 resources,
593 patches,
594 },
595 RootfsSource::DiskImage {
596 path,
597 format,
598 fstype,
599 } => Self::DiskImage {
600 sandbox,
601 path,
602 format: format.into(),
603 fstype,
604 resources,
605 patches,
606 },
607 }
608 }
609}
610
611impl Default for CloudSandboxResources {
612 fn default() -> Self {
613 let resources = SandboxResources::default();
614 Self {
615 vcpus: resources.cpus,
616 memory_mib: resources.memory_mib,
617 disk_size_mib: None,
618 }
619 }
620}
621
622impl Default for CloudSandboxComputeResources {
623 fn default() -> Self {
624 let resources = SandboxResources::default();
625 Self {
626 vcpus: resources.cpus,
627 memory_mib: resources.memory_mib,
628 }
629 }
630}
631
632impl From<CloudSandboxResources> for CloudSandboxComputeResources {
633 fn from(resources: CloudSandboxResources) -> Self {
634 Self {
635 vcpus: resources.vcpus,
636 memory_mib: resources.memory_mib,
637 }
638 }
639}
640
641impl Default for CloudCreateSandboxRequest {
642 fn default() -> Self {
643 Self::Oci {
644 sandbox: CloudSandboxSpec::default(),
645 reference: String::new(),
646 resources: CloudSandboxResources::default(),
647 patches: Vec::new(),
648 pull_policy: CloudPullPolicy::default(),
649 }
650 }
651}
652
653impl CloudRootfsSource {
654 pub fn oci(reference: impl Into<String>) -> Self {
656 Self::Oci {
657 reference: reference.into(),
658 }
659 }
660
661 pub fn oci_reference(&self) -> Option<&str> {
663 match self {
664 Self::Oci { reference } => Some(reference),
665 _ => None,
666 }
667 }
668}
669
670impl Default for CloudRootfsSource {
671 fn default() -> Self {
672 Self::oci(String::new())
673 }
674}
675
676#[cfg(test)]
677mod tests;