#![allow(missing_docs)]
#[allow(unused_imports)]
use crate::GuestPath;
#[allow(unused_imports)]
use crate::Result;
#[allow(unused_imports)]
use crate::{IntoContainerId, Mount, Stdio};
#[allow(unused_imports)]
use firkin_oci::ImageBundle;
#[allow(unused_imports)]
use firkin_types::ContainerId;
#[allow(unused_imports)]
use firkin_types::{InvalidContainerId, Size};
#[allow(unused_imports)]
use std::collections::HashSet;
#[allow(unused_imports)]
use std::ffi::OsString;
#[cfg(test)]
#[allow(unused_imports)]
use std::io;
#[allow(unused_imports)]
use std::path::Path;
#[allow(unused_imports)]
use std::path::PathBuf;
#[allow(unused_imports)]
use thiserror::Error as ThisError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum GuestFilesystem {
Ext4,
}
impl GuestFilesystem {
pub(crate) fn mount_type(self) -> &'static str {
match self {
Self::Ext4 => "ext4",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
pub enum PodValidationError {
#[error("pod declares duplicate container `{0}`")]
DuplicateContainerName(ContainerId),
#[error("pod declares duplicate emptyDir `{0}`")]
DuplicateEmptyDirName(ContainerId),
#[error("container `{container}` references unknown emptyDir `{volume_name}`")]
UnknownEmptyDirMount {
container: ContainerId,
volume_name: ContainerId,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum EmptyDirMedium {
Disk,
Memory,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EmptyDirVolume {
pub(crate) name: ContainerId,
pub(crate) medium: EmptyDirMedium,
pub(crate) size_limit: Option<Size>,
}
impl EmptyDirVolume {
pub fn disk(name: impl Into<String>) -> std::result::Result<Self, InvalidContainerId> {
Ok(Self {
name: ContainerId::new(name)?,
medium: EmptyDirMedium::Disk,
size_limit: None,
})
}
pub fn memory(
name: impl Into<String>,
size_limit: Option<Size>,
) -> std::result::Result<Self, InvalidContainerId> {
Ok(Self {
name: ContainerId::new(name)?,
medium: EmptyDirMedium::Memory,
size_limit,
})
}
#[must_use]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub const fn medium(&self) -> EmptyDirMedium {
self.medium
}
#[must_use]
pub const fn size_limit(&self) -> Option<Size> {
self.size_limit
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PodRootfsSource {
GuestPath(GuestPath),
OciBundle(Box<ImageBundle>),
Ext4Image(PathBuf),
}
impl PodRootfsSource {
#[must_use]
pub const fn guest_path(path: GuestPath) -> Self {
Self::GuestPath(path)
}
#[must_use]
pub fn oci_bundle(bundle: ImageBundle) -> Self {
Self::OciBundle(Box::new(bundle))
}
#[must_use]
pub fn ext4_image(path: impl Into<PathBuf>) -> Self {
Self::Ext4Image(path.into())
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PodVolumeMount {
pub(crate) volume_name: ContainerId,
pub(crate) container_path: PathBuf,
pub(crate) read_only: bool,
}
impl PodVolumeMount {
pub fn read_write(
volume_name: impl Into<String>,
container_path: impl Into<PathBuf>,
) -> std::result::Result<Self, InvalidContainerId> {
Ok(Self {
volume_name: ContainerId::new(volume_name)?,
container_path: container_path.into(),
read_only: false,
})
}
pub fn read_only_mount(
volume_name: impl Into<String>,
container_path: impl Into<PathBuf>,
) -> std::result::Result<Self, InvalidContainerId> {
Ok(Self {
volume_name: ContainerId::new(volume_name)?,
container_path: container_path.into(),
read_only: true,
})
}
#[must_use]
pub fn volume_name(&self) -> &str {
self.volume_name.as_str()
}
#[must_use]
pub fn container_path(&self) -> &Path {
&self.container_path
}
#[must_use]
pub const fn read_only(&self) -> bool {
self.read_only
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PodContainerSpec {
pub(crate) id: ContainerId,
pub(crate) rootfs: PodRootfsSource,
pub(crate) command: Vec<OsString>,
pub(crate) env: Vec<(OsString, OsString)>,
pub(crate) mounts: Vec<Mount>,
pub(crate) empty_dir_mounts: Vec<PodVolumeMount>,
pub(crate) stdin: Stdio,
pub(crate) stdout: Stdio,
pub(crate) stderr: Stdio,
}
impl PodContainerSpec {
pub fn new(
id: impl IntoContainerId,
rootfs: PodRootfsSource,
) -> std::result::Result<Self, InvalidContainerId> {
Ok(Self {
id: id.into_container_id()?,
rootfs,
command: Vec::new(),
env: Vec::new(),
mounts: Vec::new(),
empty_dir_mounts: Vec::new(),
stdin: Stdio::Null,
stdout: Stdio::Null,
stderr: Stdio::Null,
})
}
#[must_use]
pub const fn id(&self) -> &ContainerId {
&self.id
}
#[must_use]
pub const fn rootfs(&self) -> &PodRootfsSource {
&self.rootfs
}
#[must_use]
pub fn command_args(&self) -> &[OsString] {
&self.command
}
#[must_use]
pub fn env_vars(&self) -> &[(OsString, OsString)] {
&self.env
}
#[must_use]
pub fn empty_dir_mounts(&self) -> &[PodVolumeMount] {
&self.empty_dir_mounts
}
#[must_use]
pub fn command<I, A>(mut self, args: I) -> Self
where
I: IntoIterator<Item = A>,
A: Into<OsString>,
{
self.command = args.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
self.env.push((key.into(), value.into()));
self
}
#[must_use]
pub fn envs<I, K, V>(mut self, vars: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<OsString>,
V: Into<OsString>,
{
self.env.extend(
vars.into_iter()
.map(|(key, value)| (key.into(), value.into())),
);
self
}
#[must_use]
pub fn mount(mut self, mount: Mount) -> Self {
self.mounts.push(mount);
self
}
pub fn empty_dir_mount(
mut self,
volume_name: impl Into<String>,
container_path: impl Into<PathBuf>,
) -> std::result::Result<Self, InvalidContainerId> {
self.empty_dir_mounts
.push(PodVolumeMount::read_write(volume_name, container_path)?);
Ok(self)
}
pub fn empty_dir_mount_read_only(
mut self,
volume_name: impl Into<String>,
container_path: impl Into<PathBuf>,
) -> std::result::Result<Self, InvalidContainerId> {
self.empty_dir_mounts.push(PodVolumeMount::read_only_mount(
volume_name,
container_path,
)?);
Ok(self)
}
#[must_use]
pub const fn stdin(mut self, stdin: Stdio) -> Self {
self.stdin = stdin;
self
}
#[must_use]
pub const fn stdout(mut self, stdout: Stdio) -> Self {
self.stdout = stdout;
self
}
#[must_use]
pub const fn stderr(mut self, stderr: Stdio) -> Self {
self.stderr = stderr;
self
}
}
pub(crate) fn validate_pod_spec(
empty_dirs: &[EmptyDirVolume],
containers: &[PodContainerSpec],
) -> Result<()> {
let mut seen_empty_dirs = HashSet::new();
for volume in empty_dirs {
if !seen_empty_dirs.insert(volume.name.clone()) {
return Err(PodValidationError::DuplicateEmptyDirName(volume.name.clone()).into());
}
}
let mut seen_containers = HashSet::new();
for container in containers {
if !seen_containers.insert(container.id.clone()) {
return Err(PodValidationError::DuplicateContainerName(container.id.clone()).into());
}
validate_container_empty_dir_mounts(empty_dirs, container)?;
}
Ok(())
}
pub(crate) fn validate_container_empty_dir_mounts(
empty_dirs: &[EmptyDirVolume],
container: &PodContainerSpec,
) -> Result<()> {
for mount in &container.empty_dir_mounts {
if !empty_dirs
.iter()
.any(|volume| volume.name == mount.volume_name)
{
return Err(PodValidationError::UnknownEmptyDirMount {
container: container.id.clone(),
volume_name: mount.volume_name.clone(),
}
.into());
}
}
Ok(())
}