use crate::dfdaemon::default_proxy_server_port;
use dragonfly_client_core::error::{ErrorType, OrErr};
use dragonfly_client_core::Result;
use serde::{ser::SerializeStruct, Deserialize, Serialize};
use std::fs;
use std::net::Ipv4Addr;
use std::path::PathBuf;
use tracing::{info, instrument};
use validator::Validate;
pub const NAME: &str = "dfinit";
#[inline]
pub fn default_dfinit_config_path() -> PathBuf {
crate::default_config_dir().join("dfinit.yaml")
}
#[inline]
fn default_container_runtime_containerd_config_path() -> PathBuf {
PathBuf::from("/etc/containerd/config.toml")
}
#[inline]
fn default_container_runtime_docker_config_path() -> PathBuf {
PathBuf::from("/etc/docker/daemon.json")
}
#[inline]
fn default_container_runtime_crio_config_path() -> PathBuf {
PathBuf::from("/etc/containers/registries.conf")
}
#[inline]
fn default_container_runtime_podman_config_path() -> PathBuf {
PathBuf::from("/etc/containers/registries.conf")
}
#[inline]
fn default_container_runtime_crio_unqualified_search_registries() -> Vec<String> {
vec![
"registry.fedoraproject.org".to_string(),
"registry.access.redhat.com".to_string(),
"docker.io".to_string(),
]
}
#[inline]
fn default_container_runtime_podman_unqualified_search_registries() -> Vec<String> {
vec![
"registry.fedoraproject.org".to_string(),
"registry.access.redhat.com".to_string(),
"docker.io".to_string(),
]
}
#[inline]
fn default_proxy_addr() -> String {
format!(
"http://{}:{}",
Ipv4Addr::LOCALHOST,
default_proxy_server_port()
)
}
#[inline]
pub fn default_container_runtime_containerd_registry_capabilities() -> Vec<String> {
vec!["pull".to_string(), "resolve".to_string()]
}
#[inline]
fn default_container_runtime_containerd_proxy_all_registries() -> bool {
true
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ContainerdRegistry {
pub host_namespace: String,
pub server_addr: String,
#[serde(default = "default_container_runtime_containerd_registry_capabilities")]
pub capabilities: Vec<String>,
pub skip_verify: Option<bool>,
pub ca: Option<Vec<String>>,
}
#[derive(Debug, Clone, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Containerd {
#[serde(default = "default_container_runtime_containerd_config_path")]
pub config_path: PathBuf,
pub registries: Vec<ContainerdRegistry>,
#[serde(default = "default_container_runtime_containerd_proxy_all_registries")]
pub proxy_all_registries: bool,
}
impl Default for Containerd {
fn default() -> Self {
Self {
config_path: PathBuf::default(),
registries: Vec::default(),
proxy_all_registries: default_container_runtime_containerd_proxy_all_registries(),
}
}
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize, PartialEq, Eq)]
#[serde(default, rename_all = "camelCase")]
pub struct CRIORegistry {
pub prefix: String,
pub location: String,
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct CRIO {
#[serde(default = "default_container_runtime_crio_config_path")]
pub config_path: PathBuf,
#[serde(default = "default_container_runtime_crio_unqualified_search_registries")]
pub unqualified_search_registries: Vec<String>,
pub registries: Vec<CRIORegistry>,
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize, PartialEq, Eq)]
#[serde(default, rename_all = "camelCase")]
pub struct PodmanRegistry {
pub prefix: String,
pub location: String,
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Podman {
#[serde(default = "default_container_runtime_podman_config_path")]
pub config_path: PathBuf,
#[serde(default = "default_container_runtime_podman_unqualified_search_registries")]
pub unqualified_search_registries: Vec<String>,
pub registries: Vec<PodmanRegistry>,
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Docker {
#[serde(default = "default_container_runtime_docker_config_path")]
pub config_path: PathBuf,
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ContainerRuntime {
#[serde(flatten)]
pub config: Option<ContainerRuntimeConfig>,
}
#[derive(Debug, Clone)]
pub enum ContainerRuntimeConfig {
Containerd(Containerd),
Docker(Docker),
CRIO(CRIO),
Podman(Podman),
}
impl Serialize for ContainerRuntimeConfig {
fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match *self {
ContainerRuntimeConfig::Containerd(ref cfg) => {
let mut state = serializer.serialize_struct("containerd", 1)?;
state.serialize_field("containerd", &cfg)?;
state.end()
}
ContainerRuntimeConfig::Docker(ref cfg) => {
let mut state = serializer.serialize_struct("docker", 1)?;
state.serialize_field("docker", &cfg)?;
state.end()
}
ContainerRuntimeConfig::CRIO(ref cfg) => {
let mut state = serializer.serialize_struct("crio", 1)?;
state.serialize_field("crio", &cfg)?;
state.end()
}
ContainerRuntimeConfig::Podman(ref cfg) => {
let mut state = serializer.serialize_struct("podman", 1)?;
state.serialize_field("podman", &cfg)?;
state.end()
}
}
}
}
impl<'de> Deserialize<'de> for ContainerRuntimeConfig {
fn deserialize<D>(deserializer: D) -> std::prelude::v1::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct ContainerRuntimeHelper {
containerd: Option<Containerd>,
docker: Option<Docker>,
crio: Option<CRIO>,
podman: Option<Podman>,
}
let helper = ContainerRuntimeHelper::deserialize(deserializer)?;
match helper {
ContainerRuntimeHelper {
containerd: Some(containerd),
..
} => Ok(ContainerRuntimeConfig::Containerd(containerd)),
ContainerRuntimeHelper {
docker: Some(docker),
..
} => Ok(ContainerRuntimeConfig::Docker(docker)),
ContainerRuntimeHelper {
crio: Some(crio), ..
} => Ok(ContainerRuntimeConfig::CRIO(crio)),
ContainerRuntimeHelper {
podman: Some(podman),
..
} => Ok(ContainerRuntimeConfig::Podman(podman)),
_ => {
use serde::de::Error;
Err(D::Error::custom(
"expected containerd or docker or crio or podman",
))
}
}
}
}
#[derive(Debug, Clone, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Proxy {
#[serde(default = "default_proxy_addr")]
pub addr: String,
}
impl Default for Proxy {
fn default() -> Self {
Self {
addr: default_proxy_addr(),
}
}
}
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Config {
#[validate]
pub proxy: Proxy,
#[validate]
pub container_runtime: ContainerRuntime,
}
impl Config {
#[instrument(skip_all)]
pub fn load(path: &PathBuf) -> Result<Config> {
let content = fs::read_to_string(path)?;
let config: Config = serde_yaml::from_str(&content).or_err(ErrorType::ConfigError)?;
info!("load config from {}", path.display());
config.validate().or_err(ErrorType::ValidationError)?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_default_dfinit_config_path() {
let expected = crate::default_config_dir().join("dfinit.yaml");
assert_eq!(default_dfinit_config_path(), expected);
}
#[test]
fn test_container_runtime_default_paths() {
assert_eq!(
default_container_runtime_containerd_config_path(),
Path::new("/etc/containerd/config.toml")
);
assert_eq!(
default_container_runtime_docker_config_path(),
Path::new("/etc/docker/daemon.json")
);
assert_eq!(
default_container_runtime_crio_config_path(),
Path::new("/etc/containers/registries.conf")
);
assert_eq!(
default_container_runtime_podman_config_path(),
Path::new("/etc/containers/registries.conf")
);
}
#[test]
fn test_default_unqualified_search_registries() {
let crio_registries = default_container_runtime_crio_unqualified_search_registries();
assert_eq!(
crio_registries,
vec![
"registry.fedoraproject.org",
"registry.access.redhat.com",
"docker.io"
]
);
let podman_registries = default_container_runtime_podman_unqualified_search_registries();
assert_eq!(
podman_registries,
vec![
"registry.fedoraproject.org",
"registry.access.redhat.com",
"docker.io"
]
);
}
#[test]
fn serialize_container_runtime() {
let cfg = ContainerRuntimeConfig::Containerd(Containerd {
..Default::default()
});
let res = serde_yaml::to_string(&cfg).unwrap();
let expected = r#"
containerd:
configPath: ''
registries: []
proxyAllRegistries: true"#;
assert_eq!(expected.trim(), res.trim());
let runtime_cfg = ContainerRuntimeConfig::Docker(Docker {
config_path: PathBuf::from("/root/.dragonfly/config/dfinit/yaml"),
});
let cfg = Config {
container_runtime: ContainerRuntime {
config: Some(runtime_cfg),
},
proxy: Proxy {
addr: String::from("hello"),
},
};
let res = serde_yaml::to_string(&cfg).unwrap();
let expected = r#"
proxy:
addr: hello
containerRuntime:
docker:
configPath: /root/.dragonfly/config/dfinit/yaml"#;
assert_eq!(expected.trim(), res.trim());
let runtime_cfg = ContainerRuntimeConfig::Containerd(Containerd {
config_path: PathBuf::from("/root/.dragonfly/config/dfinit/yaml"),
..Default::default()
});
let cfg = Config {
container_runtime: ContainerRuntime {
config: Some(runtime_cfg),
},
proxy: Proxy {
addr: String::from("hello"),
},
};
let res = serde_yaml::to_string(&cfg).unwrap();
let expected = r#"
proxy:
addr: hello
containerRuntime:
containerd:
configPath: /root/.dragonfly/config/dfinit/yaml
registries: []
proxyAllRegistries: true"#;
assert_eq!(expected.trim(), res.trim());
}
#[test]
fn deserialize_container_runtime_correctly() {
let raw_data = r#"
proxy:
addr: "hello"
"#;
let cfg: Config = serde_yaml::from_str(raw_data).expect("failed to deserialize");
assert!(cfg.container_runtime.config.is_none());
assert_eq!("hello".to_string(), cfg.proxy.addr);
let raw_data = r#"
proxy:
addr: "hello"
containerRuntime:
containerd:
configPath: "test_path"
"#;
let cfg: Config = serde_yaml::from_str(raw_data).expect("failed to deserialize");
assert_eq!("hello".to_string(), cfg.proxy.addr);
if let Some(ContainerRuntimeConfig::Containerd(c)) = cfg.container_runtime.config {
assert_eq!(PathBuf::from("test_path"), c.config_path);
} else {
panic!("failed to deserialize");
}
}
#[test]
fn deserialize_container_runtime_crio_correctly() {
let raw_data = r#"
proxy:
addr: "hello"
containerRuntime:
crio:
configPath: "test_path"
unqualifiedSearchRegistries:
- "reg1"
- "reg2"
registries:
- prefix: "prefix1"
location: "location1"
- prefix: "prefix2"
location: "location2"
"#;
let cfg: Config = serde_yaml::from_str(raw_data).expect("failed to deserialize");
if let Some(ContainerRuntimeConfig::CRIO(c)) = cfg.container_runtime.config {
assert_eq!(PathBuf::from("test_path"), c.config_path);
assert_eq!(vec!["reg1", "reg2"], c.unqualified_search_registries);
assert_eq!(
vec![
CRIORegistry {
location: "location1".to_string(),
prefix: "prefix1".to_string()
},
CRIORegistry {
location: "location2".to_string(),
prefix: "prefix2".to_string()
},
],
c.registries
);
} else {
panic!("failed to deserialize");
}
}
#[test]
fn deserialize_container_runtime_podman_correctly() {
let raw_data = r#"
proxy:
addr: "hello"
containerRuntime:
podman:
configPath: "test_path"
unqualifiedSearchRegistries:
- "reg1"
- "reg2"
registries:
- prefix: "prefix1"
location: "location1"
- prefix: "prefix2"
location: "location2"
"#;
let cfg: Config = serde_yaml::from_str(raw_data).expect("failed to deserialize");
if let Some(ContainerRuntimeConfig::Podman(c)) = cfg.container_runtime.config {
assert_eq!(PathBuf::from("test_path"), c.config_path);
assert_eq!(vec!["reg1", "reg2"], c.unqualified_search_registries);
assert_eq!(
vec![
PodmanRegistry {
location: "location1".to_string(),
prefix: "prefix1".to_string()
},
PodmanRegistry {
location: "location2".to_string(),
prefix: "prefix2".to_string()
},
],
c.registries
);
} else {
panic!("failed to deserialize");
}
}
}