feat(container_runtime): support podman container runtime (#812)

Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
yxxhero 2024-11-04 10:37:53 +08:00 committed by GitHub
parent f43ce9ec47
commit 07591fa862
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 236 additions and 1 deletions

3
.gitignore vendored
View File

@ -50,3 +50,6 @@ Temporary Items
# Ignore unuseful files
scripts/certs
# Ignore .vscode folder
.vscode

1
Cargo.lock generated
View File

@ -1007,6 +1007,7 @@ dependencies = [
"dragonfly-client",
"dragonfly-client-config",
"dragonfly-client-core",
"tempfile",
"tokio",
"toml",
"toml_edit",

View File

@ -56,6 +56,12 @@ fn default_container_runtime_crio_config_path() -> PathBuf {
PathBuf::from("/etc/containers/registries.conf")
}
/// default_container_runtime_podman_config_path is the default podman configuration path.
#[inline]
fn default_container_runtime_podman_config_path() -> PathBuf {
PathBuf::from("/etc/containers/registries.conf")
}
/// default_container_runtime_crio_unqualified_search_registries is the default unqualified search registries of cri-o,
/// refer to https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#global-settings.
#[inline]
@ -67,6 +73,17 @@ fn default_container_runtime_crio_unqualified_search_registries() -> Vec<String>
]
}
/// default_container_runtime_podman_unqualified_search_registries is the default unqualified search registries of cri-o,
/// refer to https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#global-settings.
#[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(),
]
}
/// default_proxy_addr is the default proxy address of dfdaemon.
#[inline]
fn default_proxy_addr() -> String {
@ -156,6 +173,37 @@ pub struct CRIO {
pub registries: Vec<CRIORegistry>,
}
/// CRIORegistry is the registry configuration for cri-o.
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize, PartialEq, Eq)]
#[serde(default, rename_all = "camelCase")]
pub struct PodmanRegistry {
/// prefix is the prefix of the user-specified image name, refer to
/// https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#choosing-a-registry-toml-table.
pub prefix: String,
/// location accepts the same format as the prefix field, and specifies the physical location of the prefix-rooted namespace,
/// refer to https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#remapping-and-mirroring-registries.
pub location: String,
}
/// Podman is the podman configuration for dfinit.
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Podman {
/// config_path is the path of cri-o registries's configuration file.
#[serde(default = "default_container_runtime_podman_config_path")]
pub config_path: PathBuf,
/// unqualified_search_registries is an array of host[:port] registries to try when pulling an unqualified image, in order.
/// Refer to https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#global-settings.
#[serde(default = "default_container_runtime_podman_unqualified_search_registries")]
pub unqualified_search_registries: Vec<String>,
/// registries is the list of cri-o registries, refer to
/// https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#namespaced-registry-settings.
pub registries: Vec<PodmanRegistry>,
}
/// Docker is the docker configuration for dfinit.
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
#[serde(default, rename_all = "camelCase")]
@ -179,6 +227,7 @@ pub enum ContainerRuntimeConfig {
Containerd(Containerd),
Docker(Docker),
CRIO(CRIO),
Podman(Podman),
}
/// Serialize is the implementation of the Serialize trait for ContainerRuntimeConfig.
@ -203,6 +252,11 @@ impl Serialize for ContainerRuntimeConfig {
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()
}
}
}
}
@ -218,6 +272,7 @@ impl<'de> Deserialize<'de> for ContainerRuntimeConfig {
containerd: Option<Containerd>,
docker: Option<Docker>,
crio: Option<CRIO>,
podman: Option<Podman>,
}
let helper = ContainerRuntimeHelper::deserialize(deserializer)?;
@ -233,9 +288,15 @@ impl<'de> Deserialize<'de> for ContainerRuntimeConfig {
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"))
Err(D::Error::custom(
"expected containerd or docker or crio or podman",
))
}
}
}

View File

@ -25,3 +25,4 @@ tracing.workspace = true
toml_edit.workspace = true
toml.workspace = true
url.workspace = true
tempfile.workspace = true

View File

@ -21,6 +21,7 @@ use tracing::{info, instrument};
pub mod containerd;
pub mod crio;
pub mod docker;
pub mod podman;
/// Engine represents config of the container runtime engine.
#[derive(Debug, Clone)]
@ -28,6 +29,7 @@ enum Engine {
Containerd(containerd::Containerd),
Docker(docker::Docker),
Crio(crio::CRIO),
Podman(podman::Podman),
}
/// ContainerRuntime represents the container runtime manager.
@ -55,6 +57,7 @@ impl ContainerRuntime {
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,
}
}
@ -72,6 +75,9 @@ impl ContainerRuntime {
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);

View File

@ -0,0 +1,163 @@
/*
* Copyright 2024 The Dragonfly Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use dragonfly_client_config::dfinit;
use dragonfly_client_core::{
error::{ErrorType, OrErr},
Error, Result,
};
use tokio::{self, fs};
use toml_edit::{value, Array, ArrayOfTables, Item, Table, Value};
use tracing::{info, instrument};
use url::Url;
/// Podman represents the podman runtime manager.
#[derive(Debug, Clone)]
pub struct Podman {
/// config is the configuration for initializing
/// runtime environment for the dfdaemon.
config: dfinit::Podman,
/// proxy_config is the configuration for the dfdaemon's proxy server.
proxy_config: dfinit::Proxy,
}
/// Podman implements the podman runtime manager.
impl Podman {
/// new creates a new podman runtime manager.
#[instrument(skip_all)]
pub fn new(config: dfinit::Podman, proxy_config: dfinit::Proxy) -> Self {
Self {
config,
proxy_config,
}
}
/// run runs the podman runtime to initialize
/// runtime environment for the dfdaemon.
#[instrument(skip_all)]
pub async fn run(&self) -> Result<()> {
let mut registries_config_table = toml_edit::DocumentMut::new();
registries_config_table.set_implicit(true);
// Add unqualified-search-registries to registries config.
let mut unqualified_search_registries = Array::default();
for unqualified_search_registry in self.config.unqualified_search_registries.clone() {
unqualified_search_registries.push(Value::from(unqualified_search_registry));
}
registries_config_table.insert(
"unqualified-search-registries",
value(unqualified_search_registries),
);
// Parse proxy address to get host and port.
let proxy_url =
Url::parse(self.proxy_config.addr.as_str()).or_err(ErrorType::ParseError)?;
let proxy_host = proxy_url
.host_str()
.ok_or(Error::Unknown("host not found".to_string()))?;
let proxy_port = proxy_url
.port_or_known_default()
.ok_or(Error::Unknown("port not found".to_string()))?;
let proxy_location = format!("{}:{}", proxy_host, proxy_port);
// Add registries to the registries config.
let mut registries_table = ArrayOfTables::new();
for registry in self.config.registries.clone() {
info!("add registry: {:?}", registry);
let mut registry_mirror_table = Table::new();
registry_mirror_table.set_implicit(true);
registry_mirror_table.insert("insecure", value(true));
registry_mirror_table.insert("location", value(proxy_location.as_str()));
let mut registry_mirrors_table = ArrayOfTables::new();
registry_mirrors_table.push(registry_mirror_table);
let mut registry_table = Table::new();
registry_table.set_implicit(true);
registry_table.insert("prefix", value(registry.prefix));
registry_table.insert("location", value(registry.location));
registry_table.insert("mirror", Item::ArrayOfTables(registry_mirrors_table));
registries_table.push(registry_table);
}
registries_config_table.insert("registry", Item::ArrayOfTables(registries_table));
let registries_config_dir = self
.config
.config_path
.parent()
.ok_or(Error::Unknown("invalid config path".to_string()))?;
fs::create_dir_all(registries_config_dir.as_os_str()).await?;
fs::write(
self.config.config_path.as_os_str(),
registries_config_table.to_string().as_bytes(),
)
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_podman_config() {
use tempfile::NamedTempFile;
let podman_config_file = NamedTempFile::new().unwrap();
let podman = Podman::new(
dfinit::Podman {
config_path: podman_config_file.path().to_path_buf(),
registries: vec![dfinit::PodmanRegistry {
prefix: "registry.example.com".into(),
location: "registry.example.com".into(),
}],
unqualified_search_registries: vec!["registry.example.com".into()],
},
dfinit::Proxy {
addr: "http://127.0.0.1:5000".into(),
},
);
let result = podman.run().await;
assert!(result.is_ok());
// get the contents of the file
let contents = fs::read_to_string(podman_config_file.path().to_path_buf())
.await
.unwrap();
let expected_contents = r#"unqualified-search-registries = ["registry.example.com"]
[[registry]]
prefix = "registry.example.com"
location = "registry.example.com"
[[registry.mirror]]
insecure = true
location = "127.0.0.1:5000"
"#;
// assert that the contents of the file are as expected
assert_eq!(contents, expected_contents);
// clean up
fs::remove_file(podman_config_file.path().to_path_buf())
.await
.unwrap();
}
}