use std::{
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
ops::Not,
path::PathBuf,
};
use serde::{Deserialize, Serialize};
use crate::{service::ByteValue, Extensions, Identifier};
use super::{AbsolutePath, HostPath, SELinux, ShortOptions, ShortVolume};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Mount {
Volume(Volume),
Bind(Bind),
Tmpfs(Tmpfs),
#[serde(rename = "npipe")]
NamedPipe(NamedPipe),
Cluster(Cluster),
}
impl Mount {
#[doc(alias = "type")]
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::Volume(_) => "volume",
Self::Bind(_) => "bind",
Self::Tmpfs(_) => "tmpfs",
Self::NamedPipe(_) => "npipe",
Self::Cluster(_) => "cluster",
}
}
#[must_use]
pub const fn is_volume(&self) -> bool {
matches!(self, Self::Volume(..))
}
#[must_use]
pub const fn as_volume(&self) -> Option<&Volume> {
if let Self::Volume(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn is_bind(&self) -> bool {
matches!(self, Self::Bind(..))
}
#[must_use]
pub const fn as_bind(&self) -> Option<&Bind> {
if let Self::Bind(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn is_tmpfs(&self) -> bool {
matches!(self, Self::Tmpfs(..))
}
#[must_use]
pub const fn as_tmpfs(&self) -> Option<&Tmpfs> {
if let Self::Tmpfs(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn is_named_pipe(&self) -> bool {
matches!(self, Self::NamedPipe(..))
}
#[must_use]
pub const fn as_named_pipe(&self) -> Option<&NamedPipe> {
if let Self::NamedPipe(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn is_cluster(&self) -> bool {
matches!(self, Self::Cluster(..))
}
#[must_use]
pub const fn as_cluster(&self) -> Option<&Cluster> {
if let Self::Cluster(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn common(&self) -> &Common {
match self {
Self::Volume(mount) => &mount.common,
Self::Bind(mount) => &mount.common,
Self::Tmpfs(mount) => &mount.common,
Self::NamedPipe(mount) => &mount.common,
Self::Cluster(mount) => &mount.common,
}
}
#[must_use]
pub fn source_to_string(&self) -> Option<String> {
match self {
Self::Volume(mount) => mount.source.as_ref().map(Identifier::to_string),
Self::Bind(mount) => Some(mount.source.as_path().display().to_string()),
Self::Tmpfs(_) => None,
Self::NamedPipe(mount) => Some(mount.source.as_path().display().to_string()),
Self::Cluster(mount) => Some(mount.source.clone()),
}
}
pub fn into_short(self) -> Result<ShortVolume, Self> {
match self {
Self::Volume(volume) => volume.into_short().map_err(Self::Volume),
Self::Bind(bind) => bind.into_short().map_err(Self::Bind),
_ => Err(self),
}
}
}
impl From<ShortVolume> for Mount {
fn from(value: ShortVolume) -> Self {
value.into_long()
}
}
impl From<Volume> for Mount {
fn from(value: Volume) -> Self {
Self::Volume(value)
}
}
impl From<Bind> for Mount {
fn from(value: Bind) -> Self {
Self::Bind(value)
}
}
impl From<Tmpfs> for Mount {
fn from(value: Tmpfs) -> Self {
Self::Tmpfs(value)
}
}
impl From<NamedPipe> for Mount {
fn from(value: NamedPipe) -> Self {
Self::NamedPipe(value)
}
}
impl From<Cluster> for Mount {
fn from(value: Cluster) -> Self {
Self::Cluster(value)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Volume {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<Identifier>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub volume: Option<VolumeOptions>,
#[serde(flatten)]
pub common: Common,
}
impl Volume {
#[must_use]
pub const fn new(common: Common) -> Self {
Self {
source: None,
volume: None,
common,
}
}
pub fn into_short(self) -> Result<ShortVolume, Self> {
match self {
Self {
source,
volume,
common:
Common {
target: container_path,
read_only,
consistency: None,
extensions,
},
} if volume.as_ref().map_or(true, VolumeOptions::is_empty) && extensions.is_empty() => {
Ok(ShortVolume {
container_path,
options: source.map(|source| ShortOptions {
source: source.into(),
read_only,
selinux: None,
}),
})
}
_ => Err(self),
}
}
}
impl From<Common> for Volume {
fn from(common: Common) -> Self {
Self::new(common)
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq)]
pub struct VolumeOptions {
#[serde(default, skip_serializing_if = "Not::not")]
pub nocopy: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subpath: Option<PathBuf>,
#[serde(flatten)]
pub extensions: Extensions,
}
impl VolumeOptions {
#[must_use]
pub fn is_empty(&self) -> bool {
let Self {
nocopy,
subpath,
extensions,
} = self;
!nocopy && subpath.is_none() && extensions.is_empty()
}
}
impl PartialEq for VolumeOptions {
fn eq(&self, other: &Self) -> bool {
let Self {
nocopy,
subpath,
extensions,
} = self;
*nocopy == other.nocopy
&& *subpath == other.subpath
&& extensions.as_slice() == other.extensions.as_slice()
}
}
impl Hash for VolumeOptions {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self {
nocopy,
subpath,
extensions,
} = self;
nocopy.hash(state);
subpath.hash(state);
extensions.as_slice().hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Bind {
pub source: HostPath,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bind: Option<BindOptions>,
#[serde(flatten)]
pub common: Common,
}
impl Bind {
#[must_use]
pub const fn new(source: HostPath, common: Common) -> Self {
Self {
source,
bind: None,
common,
}
}
pub fn into_short(self) -> Result<ShortVolume, Self> {
match self {
Self {
source,
bind,
common:
Common {
target: container_path,
read_only,
consistency: None,
extensions,
},
} if bind.as_ref().map_or(
true,
|BindOptions {
propagation,
create_host_path,
selinux: _,
extensions,
}| {
propagation.is_none() && *create_host_path && extensions.is_empty()
},
) && extensions.is_empty() =>
{
Ok(ShortVolume {
container_path,
options: Some(ShortOptions {
source: source.into(),
read_only,
selinux: bind.and_then(|options| options.selinux),
}),
})
}
_ => Err(self),
}
}
}
impl From<(HostPath, Common)> for Bind {
fn from((source, common): (HostPath, Common)) -> Self {
Self::new(source, common)
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq)]
pub struct BindOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub propagation: Option<BindPropagation>,
#[serde(default, skip_serializing_if = "Not::not")]
pub create_host_path: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selinux: Option<SELinux>,
#[serde(flatten)]
pub extensions: Extensions,
}
impl From<Option<BindPropagation>> for BindOptions {
fn from(propagation: Option<BindPropagation>) -> Self {
Self {
propagation,
..Self::default()
}
}
}
impl From<BindPropagation> for BindOptions {
fn from(propagation: BindPropagation) -> Self {
Some(propagation).into()
}
}
impl From<Option<SELinux>> for BindOptions {
fn from(selinux: Option<SELinux>) -> Self {
Self {
selinux,
..Self::default()
}
}
}
impl From<SELinux> for BindOptions {
fn from(selinux: SELinux) -> Self {
Some(selinux).into()
}
}
impl BindOptions {
#[must_use]
pub fn is_empty(&self) -> bool {
let Self {
propagation,
create_host_path,
selinux,
extensions,
} = self;
propagation.is_none() && !create_host_path && selinux.is_none() && extensions.is_empty()
}
}
impl PartialEq for BindOptions {
fn eq(&self, other: &Self) -> bool {
let Self {
propagation,
create_host_path,
selinux,
extensions,
} = self;
*propagation == other.propagation
&& *create_host_path == other.create_host_path
&& *selinux == other.selinux
&& extensions.as_slice() == other.extensions.as_slice()
}
}
impl Hash for BindOptions {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self {
propagation,
create_host_path,
selinux,
extensions,
} = self;
propagation.hash(state);
create_host_path.hash(state);
selinux.hash(state);
extensions.as_slice().hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum BindPropagation {
Private,
Shared,
Slave,
Unbindable,
#[default]
RPrivate,
RShared,
RSlave,
RUnbindable,
}
impl BindPropagation {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Private => "private",
Self::Shared => "shared",
Self::Slave => "slave",
Self::Unbindable => "unbindable",
Self::RPrivate => "rprivate",
Self::RShared => "rshared",
Self::RSlave => "rslave",
Self::RUnbindable => "runbindable",
}
}
}
impl AsRef<str> for BindPropagation {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Display for BindPropagation {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Tmpfs {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tmpfs: Option<TmpfsOptions>,
#[serde(flatten)]
pub common: Common,
}
impl Tmpfs {
#[must_use]
pub const fn new(common: Common) -> Self {
Self {
tmpfs: None,
common,
}
}
#[must_use]
pub fn from_target(target: AbsolutePath) -> Self {
Self::new(Common::new(target))
}
}
impl From<Common> for Tmpfs {
fn from(common: Common) -> Self {
Self::new(common)
}
}
impl From<AbsolutePath> for Tmpfs {
fn from(target: AbsolutePath) -> Self {
Self::from_target(target)
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq)]
pub struct TmpfsOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<ByteValue>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mode: Option<u32>,
#[serde(flatten)]
pub extensions: Extensions,
}
impl TmpfsOptions {
#[must_use]
pub fn is_empty(&self) -> bool {
let Self {
size,
mode,
extensions,
} = self;
size.is_none() && mode.is_none() && extensions.is_empty()
}
}
impl PartialEq for TmpfsOptions {
fn eq(&self, other: &Self) -> bool {
let Self {
size,
mode,
extensions,
} = self;
*size == other.size
&& *mode == other.mode
&& extensions.as_slice() == other.extensions.as_slice()
}
}
impl Hash for TmpfsOptions {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self {
size,
mode,
extensions,
} = self;
size.hash(state);
mode.hash(state);
extensions.as_slice().hash(state);
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct NamedPipe {
pub source: HostPath,
#[serde(flatten)]
pub common: Common,
}
impl From<(HostPath, Common)> for NamedPipe {
fn from((source, common): (HostPath, Common)) -> Self {
Self { source, common }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Cluster {
pub source: String,
#[serde(flatten)]
pub common: Common,
}
impl From<(String, Common)> for Cluster {
fn from((source, common): (String, Common)) -> Self {
Self { source, common }
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Eq)]
pub struct Common {
pub target: AbsolutePath,
#[serde(default, skip_serializing_if = "Not::not")]
pub read_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub consistency: Option<String>,
#[serde(flatten)]
pub extensions: Extensions,
}
impl Common {
#[must_use]
pub fn new(target: AbsolutePath) -> Self {
Self {
target,
read_only: false,
consistency: None,
extensions: Extensions::default(),
}
}
}
impl PartialEq for Common {
fn eq(&self, other: &Self) -> bool {
let Self {
target,
read_only,
consistency,
extensions,
} = self;
*target == other.target
&& *read_only == other.read_only
&& *consistency == other.consistency
&& extensions.as_slice() == other.extensions.as_slice()
}
}
impl Hash for Common {
fn hash<H: Hasher>(&self, state: &mut H) {
let Self {
target,
read_only,
consistency,
extensions,
} = self;
target.hash(state);
read_only.hash(state);
consistency.hash(state);
extensions.as_slice().hash(state);
}
}
impl From<AbsolutePath> for Common {
fn from(target: AbsolutePath) -> Self {
Self::new(target)
}
}