use dragonfly_client_config::dfinit::{Config, ContainerRuntimeConfig};
use dragonfly_client_core::Result;
use std::path::PathBuf;
use tokio::fs;
use tracing::{info, instrument};
pub mod containerd;
pub mod crio;
pub mod docker;
pub mod podman;
#[derive(Debug, Clone)]
enum Engine {
Containerd(containerd::Containerd),
Docker(docker::Docker),
Crio(crio::CRIO),
Podman(podman::Podman),
}
pub struct ContainerRuntime {
engine: Option<Engine>,
restart_sentinel: PathBuf,
}
impl ContainerRuntime {
#[instrument(skip_all)]
pub fn new(config: &Config, restart_sentinel: PathBuf) -> Self {
Self {
engine: Self::get_engine(config),
restart_sentinel,
}
}
#[instrument(skip_all)]
pub async fn run(&self) -> Result<()> {
let restart_required = match &self.engine {
None => false,
Some(Engine::Containerd(containerd)) => containerd.run().await?,
Some(Engine::Docker(docker)) => docker.run().await?,
Some(Engine::Crio(crio)) => crio.run().await?,
Some(Engine::Podman(podman)) => podman.run().await?,
};
if restart_required {
if let Some(parent) = self.restart_sentinel.parent() {
fs::create_dir_all(parent).await?;
}
fs::write(&self.restart_sentinel, b"").await?;
info!(
"container runtime restart required, created sentinel {}",
self.restart_sentinel.display()
);
}
Ok(())
}
#[instrument(skip_all)]
fn get_engine(config: &Config) -> Option<Engine> {
if let Some(ref container_runtime_config) = config.container_runtime.config {
let engine = match container_runtime_config {
ContainerRuntimeConfig::Containerd(containerd) => Engine::Containerd(
containerd::Containerd::new(containerd.clone(), config.proxy.clone()),
),
ContainerRuntimeConfig::Docker(docker) => {
Engine::Docker(docker::Docker::new(docker.clone(), config.proxy.clone()))
}
ContainerRuntimeConfig::CRIO(crio) => {
Engine::Crio(crio::CRIO::new(crio.clone(), config.proxy.clone()))
}
ContainerRuntimeConfig::Podman(podman) => {
Engine::Podman(podman::Podman::new(podman.clone(), config.proxy.clone()))
}
};
info!("container runtime engine is {:?}", engine);
return Some(engine);
}
info!("container runtime engine is not set");
None
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::type_complexity)]
use super::*;
use dragonfly_client_config::dfinit;
#[test]
fn new_picks_engine_matching_config() {
let test_cases: Vec<(Option<ContainerRuntimeConfig>, fn(Option<&Engine>))> = vec![
(None, |engine| assert!(engine.is_none())),
(
Some(ContainerRuntimeConfig::Containerd(Default::default())),
|engine| assert!(matches!(engine, Some(Engine::Containerd(_)))),
),
(
Some(ContainerRuntimeConfig::Docker(Default::default())),
|engine| assert!(matches!(engine, Some(Engine::Docker(_)))),
),
(
Some(ContainerRuntimeConfig::CRIO(Default::default())),
|engine| assert!(matches!(engine, Some(Engine::Crio(_)))),
),
(
Some(ContainerRuntimeConfig::Podman(Default::default())),
|engine| assert!(matches!(engine, Some(Engine::Podman(_)))),
),
];
for (container_runtime_config, expect) in test_cases {
let runtime = ContainerRuntime::new(
&Config {
container_runtime: dfinit::ContainerRuntime {
config: container_runtime_config,
},
..Default::default()
},
PathBuf::default(),
);
expect(runtime.engine.as_ref());
}
}
#[tokio::test]
async fn run_returns_ok_without_engine() {
let temp_dir = tempfile::tempdir().unwrap();
let restart_sentinel = temp_dir.path().join("restart-required");
let runtime = ContainerRuntime::new(&Config::default(), restart_sentinel.clone());
assert!(runtime.run().await.is_ok());
assert!(!restart_sentinel.exists());
}
#[tokio::test]
async fn run_creates_restart_sentinel_only_when_config_rewritten() {
let test_cases = vec![
(
r#"
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "{certs_dir}"
"#,
false,
),
(
r#"
[plugins."io.containerd.grpc.v1.cri"]
"#,
true,
),
];
for (containerd_config, expected) in test_cases {
let temp_dir = tempfile::tempdir().unwrap();
let config_path = temp_dir.path().join("config.toml");
let certs_dir = temp_dir.path().join("certs.d");
fs::write(
&config_path,
containerd_config.replace("{certs_dir}", certs_dir.to_str().unwrap()),
)
.await
.unwrap();
let restart_sentinel = temp_dir.path().join("dfinit").join("restart-required");
let runtime = ContainerRuntime::new(
&Config {
container_runtime: dfinit::ContainerRuntime {
config: Some(ContainerRuntimeConfig::Containerd(dfinit::Containerd {
config_path,
cri_plugin_id: None,
registries: vec![],
proxy_all_registries: false,
})),
},
..Default::default()
},
restart_sentinel.clone(),
);
runtime.run().await.unwrap();
assert_eq!(restart_sentinel.exists(), expected);
}
}
}