use std::collections::BTreeMap;
pub const PODMAN_AUTO_UPDATE_LABEL: &str = "io.containers.autoupdate";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PodmanAutoUpdatePolicy {
Registry,
Local,
}
impl PodmanAutoUpdatePolicy {
pub fn as_label_value(self) -> &'static str {
match self {
Self::Registry => "registry",
Self::Local => "local",
}
}
pub fn as_label_arg(self) -> String {
format!("{PODMAN_AUTO_UPDATE_LABEL}={}", self.as_label_value())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuadletUnitKind {
Container,
Pod,
Volume,
Network,
Kube,
}
impl QuadletUnitKind {
pub fn extension(self) -> &'static str {
match self {
Self::Container => "container",
Self::Pod => "pod",
Self::Volume => "volume",
Self::Network => "network",
Self::Kube => "kube",
}
}
pub fn section(self) -> &'static str {
match self {
Self::Container => "Container",
Self::Pod => "Pod",
Self::Volume => "Volume",
Self::Network => "Network",
Self::Kube => "Kube",
}
}
}
#[derive(Debug, Clone)]
pub struct QuadletUnit {
kind: QuadletUnitKind,
name: String,
sections: BTreeMap<String, Vec<(String, String)>>,
}
impl QuadletUnit {
pub fn new(kind: QuadletUnitKind, name: impl Into<String>) -> Self {
Self {
kind,
name: name.into(),
sections: BTreeMap::new(),
}
}
pub fn container(name: impl Into<String>, image: impl Into<String>) -> Self {
Self::new(QuadletUnitKind::Container, name).entry("Container", "Image", image)
}
pub fn pod(name: impl Into<String>) -> Self {
Self::new(QuadletUnitKind::Pod, name)
}
pub fn kube(name: impl Into<String>, yaml: impl Into<String>) -> Self {
Self::new(QuadletUnitKind::Kube, name).entry("Kube", "Yaml", yaml)
}
pub fn entry(
mut self,
section: impl Into<String>,
key: impl Into<String>,
value: impl Into<String>,
) -> Self {
self.sections
.entry(section.into())
.or_default()
.push((key.into(), value.into()));
self
}
pub fn auto_update(self, policy: PodmanAutoUpdatePolicy) -> Self {
self.entry(
"Container",
"Label",
format!("{PODMAN_AUTO_UPDATE_LABEL}={}", policy.as_label_value()),
)
}
pub fn kind(&self) -> QuadletUnitKind {
self.kind
}
pub fn file_name(&self) -> String {
format!("{}.{}", self.name, self.kind.extension())
}
pub fn render(&self) -> String {
let mut output = String::new();
for (section, entries) in &self.sections {
output.push('[');
output.push_str(section);
output.push_str("]\n");
for (key, value) in entries {
output.push_str(key);
output.push('=');
output.push_str(value);
output.push('\n');
}
output.push('\n');
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auto_update_label_values_match_podman() {
assert_eq!(
PodmanAutoUpdatePolicy::Registry.as_label_value(),
"registry"
);
assert_eq!(
PodmanAutoUpdatePolicy::Local.as_label_arg(),
"io.containers.autoupdate=local"
);
}
#[test]
fn container_quadlet_renders_expected_file() {
let unit = QuadletUnit::container("web", "docker.io/library/caddy:latest")
.entry("Container", "PublishPort", "8080:80")
.auto_update(PodmanAutoUpdatePolicy::Registry)
.entry("Service", "Restart", "always");
let rendered = unit.render();
assert_eq!(unit.file_name(), "web.container");
assert!(rendered.contains("[Container]\n"));
assert!(rendered.contains("Image=docker.io/library/caddy:latest\n"));
assert!(rendered.contains("PublishPort=8080:80\n"));
assert!(rendered.contains("Label=io.containers.autoupdate=registry\n"));
assert!(rendered.contains("[Service]\nRestart=always\n"));
}
#[test]
fn kube_quadlet_uses_kube_section() {
let unit = QuadletUnit::kube("stack", "/srv/stack.yaml");
assert_eq!(unit.kind(), QuadletUnitKind::Kube);
assert_eq!(unit.file_name(), "stack.kube");
assert!(unit.render().contains("[Kube]\nYaml=/srv/stack.yaml\n"));
}
}