feat(container_runtime): support podman container runtime (#812)
Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
f43ce9ec47
commit
07591fa862
|
|
@ -50,3 +50,6 @@ Temporary Items
|
||||||
|
|
||||||
# Ignore unuseful files
|
# Ignore unuseful files
|
||||||
scripts/certs
|
scripts/certs
|
||||||
|
|
||||||
|
# Ignore .vscode folder
|
||||||
|
.vscode
|
||||||
|
|
|
||||||
|
|
@ -1007,6 +1007,7 @@ dependencies = [
|
||||||
"dragonfly-client",
|
"dragonfly-client",
|
||||||
"dragonfly-client-config",
|
"dragonfly-client-config",
|
||||||
"dragonfly-client-core",
|
"dragonfly-client-core",
|
||||||
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
"toml_edit",
|
"toml_edit",
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,12 @@ fn default_container_runtime_crio_config_path() -> PathBuf {
|
||||||
PathBuf::from("/etc/containers/registries.conf")
|
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,
|
/// 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.
|
/// refer to https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md#global-settings.
|
||||||
#[inline]
|
#[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.
|
/// default_proxy_addr is the default proxy address of dfdaemon.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default_proxy_addr() -> String {
|
fn default_proxy_addr() -> String {
|
||||||
|
|
@ -156,6 +173,37 @@ pub struct CRIO {
|
||||||
pub registries: Vec<CRIORegistry>,
|
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.
|
/// Docker is the docker configuration for dfinit.
|
||||||
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Default, Validate, Deserialize, Serialize)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
|
@ -179,6 +227,7 @@ pub enum ContainerRuntimeConfig {
|
||||||
Containerd(Containerd),
|
Containerd(Containerd),
|
||||||
Docker(Docker),
|
Docker(Docker),
|
||||||
CRIO(CRIO),
|
CRIO(CRIO),
|
||||||
|
Podman(Podman),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize is the implementation of the Serialize trait for ContainerRuntimeConfig.
|
/// Serialize is the implementation of the Serialize trait for ContainerRuntimeConfig.
|
||||||
|
|
@ -203,6 +252,11 @@ impl Serialize for ContainerRuntimeConfig {
|
||||||
state.serialize_field("crio", &cfg)?;
|
state.serialize_field("crio", &cfg)?;
|
||||||
state.end()
|
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>,
|
containerd: Option<Containerd>,
|
||||||
docker: Option<Docker>,
|
docker: Option<Docker>,
|
||||||
crio: Option<CRIO>,
|
crio: Option<CRIO>,
|
||||||
|
podman: Option<Podman>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let helper = ContainerRuntimeHelper::deserialize(deserializer)?;
|
let helper = ContainerRuntimeHelper::deserialize(deserializer)?;
|
||||||
|
|
@ -233,9 +288,15 @@ impl<'de> Deserialize<'de> for ContainerRuntimeConfig {
|
||||||
ContainerRuntimeHelper {
|
ContainerRuntimeHelper {
|
||||||
crio: Some(crio), ..
|
crio: Some(crio), ..
|
||||||
} => Ok(ContainerRuntimeConfig::CRIO(crio)),
|
} => Ok(ContainerRuntimeConfig::CRIO(crio)),
|
||||||
|
ContainerRuntimeHelper {
|
||||||
|
podman: Some(podman),
|
||||||
|
..
|
||||||
|
} => Ok(ContainerRuntimeConfig::Podman(podman)),
|
||||||
_ => {
|
_ => {
|
||||||
use serde::de::Error;
|
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",
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,4 @@ tracing.workspace = true
|
||||||
toml_edit.workspace = true
|
toml_edit.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use tracing::{info, instrument};
|
||||||
pub mod containerd;
|
pub mod containerd;
|
||||||
pub mod crio;
|
pub mod crio;
|
||||||
pub mod docker;
|
pub mod docker;
|
||||||
|
pub mod podman;
|
||||||
|
|
||||||
/// Engine represents config of the container runtime engine.
|
/// Engine represents config of the container runtime engine.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -28,6 +29,7 @@ enum Engine {
|
||||||
Containerd(containerd::Containerd),
|
Containerd(containerd::Containerd),
|
||||||
Docker(docker::Docker),
|
Docker(docker::Docker),
|
||||||
Crio(crio::CRIO),
|
Crio(crio::CRIO),
|
||||||
|
Podman(podman::Podman),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ContainerRuntime represents the container runtime manager.
|
/// ContainerRuntime represents the container runtime manager.
|
||||||
|
|
@ -55,6 +57,7 @@ impl ContainerRuntime {
|
||||||
Some(Engine::Containerd(containerd)) => containerd.run().await,
|
Some(Engine::Containerd(containerd)) => containerd.run().await,
|
||||||
Some(Engine::Docker(docker)) => docker.run().await,
|
Some(Engine::Docker(docker)) => docker.run().await,
|
||||||
Some(Engine::Crio(crio)) => crio.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) => {
|
ContainerRuntimeConfig::CRIO(crio) => {
|
||||||
Engine::Crio(crio::CRIO::new(crio.clone(), config.proxy.clone()))
|
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);
|
info!("container runtime engine is {:?}", engine);
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue