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