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 rate_limiter: None,
496 trust_host_cas: false,
497 outbound_proxy: None,
498 };
499 let runtime = SandboxRuntimeOptions {
500 workdir: self.runtime.workdir,
501 shell: self.runtime.shell,
502 scripts: self.runtime.scripts,
503 entrypoint: self.runtime.entrypoint,
504 cmd: self.runtime.cmd,
505 hostname: None,
506 user: self.runtime.user,
507 log_level: self.runtime.log_level,
508 metrics_sample_interval_ms: None,
509 disable_metrics_sample: false,
510 };
511
512 Ok(SandboxSpec {
513 name: self.name,
514 image,
515 resources,
516 runtime,
517 env: self.env,
518 labels: self.labels,
519 rlimits: self.rlimits.into_iter().map(Into::into).collect(),
520 mounts: self.mounts.into_iter().map(Into::into).collect(),
521 patches: patches.into_iter().map(Into::into).collect(),
522 network,
523 vsock: VsockSpec::default(),
524 init: self.init,
525 pull_policy: pull_policy.into(),
526 security_profile: self.security_profile,
527 deployment_profile: DeploymentProfile::default(),
528 lifecycle: self.lifecycle,
529 })
530 }
531}
532
533impl From<SandboxSpec> for CloudCreateSandboxRequest {
534 fn from(spec: SandboxSpec) -> Self {
535 let resources = CloudSandboxComputeResources {
536 vcpus: spec.resources.cpus,
537 memory_mib: spec.resources.memory_mib,
538 };
539 let patches = spec.patches.into_iter().map(Into::into).collect();
540 let pull_policy = spec.pull_policy.into();
541 let sandbox = CloudSandboxSpec {
542 name: spec.name,
543 runtime: CloudSandboxRuntimeOptions {
544 workdir: spec.runtime.workdir,
545 shell: spec.runtime.shell,
546 scripts: spec.runtime.scripts,
547 entrypoint: spec.runtime.entrypoint,
548 cmd: spec.runtime.cmd,
549 user: spec.runtime.user,
550 log_level: spec.runtime.log_level,
551 },
552 env: spec.env,
553 labels: spec.labels,
554 rlimits: spec.rlimits.into_iter().map(Into::into).collect(),
555 mounts: spec.mounts.into_iter().map(Into::into).collect(),
556 network: CloudNetworkSpec {
557 enabled: spec.network.enabled,
558 policy: spec.network.policy,
559 secrets: spec.network.secrets.map(Into::into),
560 strict: spec.network.strict,
561 max_tcp_connections: spec.network.max_tcp_connections,
562 max_udp_connections: spec.network.max_udp_connections,
563 },
564 init: spec.init,
565 security_profile: spec.security_profile,
566 lifecycle: spec.lifecycle,
567 };
568
569 match spec.image {
570 RootfsSource::Oci(oci) => Self::Oci {
571 sandbox,
572 reference: oci.reference,
573 resources: CloudSandboxResources {
574 vcpus: resources.vcpus,
575 memory_mib: resources.memory_mib,
576 disk_size_mib: match oci.root_disk {
577 Some(RootDisk::Managed { size_mib }) => size_mib,
578 _ => None,
579 },
580 },
581 patches,
582 pull_policy,
583 },
584 RootfsSource::Bind { path, .. } => Self::Bind {
585 sandbox,
586 path,
587 resources,
588 patches,
589 },
590 RootfsSource::DiskImage {
591 path,
592 format,
593 fstype,
594 } => Self::DiskImage {
595 sandbox,
596 path,
597 format: format.into(),
598 fstype,
599 resources,
600 patches,
601 },
602 }
603 }
604}
605
606impl Default for CloudSandboxResources {
607 fn default() -> Self {
608 let resources = SandboxResources::default();
609 Self {
610 vcpus: resources.cpus,
611 memory_mib: resources.memory_mib,
612 disk_size_mib: None,
613 }
614 }
615}
616
617impl Default for CloudSandboxComputeResources {
618 fn default() -> Self {
619 let resources = SandboxResources::default();
620 Self {
621 vcpus: resources.cpus,
622 memory_mib: resources.memory_mib,
623 }
624 }
625}
626
627impl From<CloudSandboxResources> for CloudSandboxComputeResources {
628 fn from(resources: CloudSandboxResources) -> Self {
629 Self {
630 vcpus: resources.vcpus,
631 memory_mib: resources.memory_mib,
632 }
633 }
634}
635
636impl Default for CloudCreateSandboxRequest {
637 fn default() -> Self {
638 Self::Oci {
639 sandbox: CloudSandboxSpec::default(),
640 reference: String::new(),
641 resources: CloudSandboxResources::default(),
642 patches: Vec::new(),
643 pull_policy: CloudPullPolicy::default(),
644 }
645 }
646}
647
648impl CloudRootfsSource {
649 pub fn oci(reference: impl Into<String>) -> Self {
651 Self::Oci {
652 reference: reference.into(),
653 }
654 }
655
656 pub fn oci_reference(&self) -> Option<&str> {
658 match self {
659 Self::Oci { reference } => Some(reference),
660 _ => None,
661 }
662 }
663}
664
665impl Default for CloudRootfsSource {
666 fn default() -> Self {
667 Self::oci(String::new())
668 }
669}
670
671#[cfg(test)]
672mod tests;