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