mod create;
mod plan;
mod remove;
use std::collections::HashMap;
use std::path::Path;
use crate::compose::types::{ComposeFile, Service};
use crate::error::Result;
use crate::libpod::types::container::Secret;
use plan::{collect_native_plans, host_file_secret_mode, Payload};
use super::Engine;
impl Engine {
pub(super) async fn build_native_secrets(
&self,
service: &Service,
file: &ComposeFile,
) -> Result<Vec<Secret>> {
let plans = collect_native_plans(&self.project, service, file, &self.base_dir)?;
let mut secrets = Vec::with_capacity(plans.len());
for plan in plans {
if plan.payload.is_none() {
self.ensure_external_exists("secret", "secrets", &plan.source)
.await?;
}
let mode = match (&plan.payload, plan.mode) {
(Some(Payload::File(path)), None) => Some(host_file_secret_mode(path)),
_ => plan.mode,
};
secrets.push(Secret {
source: plan.source,
target: Some(plan.target),
uid: plan.uid,
gid: plan.gid,
mode,
});
}
Ok(secrets)
}
}
fn collect_payload_union(
project: &str,
file: &ComposeFile,
base_dir: &Path,
) -> Result<HashMap<String, Payload>> {
let mut payloads: HashMap<String, Payload> = HashMap::new();
for service in file.services.values() {
for plan in collect_native_plans(project, service, file, base_dir)? {
if let Some(payload) = plan.payload {
payloads.entry(plan.source).or_insert(payload);
}
}
}
Ok(payloads)
}
#[cfg(test)]
pub(super) mod tests_support {
use super::*;
#[cfg(unix)]
use crate::engine::fake_podman;
#[cfg(unix)]
pub(in crate::engine::secrets) fn file_with_content_secrets(n: usize) -> ComposeFile {
let refs: String = (1..=n).map(|i| format!(" - s{i}\n")).collect();
let defs: String = (1..=n)
.map(|i| format!(" s{i}: {{content: \"v{i}\"}}\n"))
.collect();
crate::compose::parse_str(&format!(
"services:\n app:\n image: alpine\n secrets:\n{refs}secrets:\n{defs}"
))
.expect("fixture compose file should parse")
}
#[cfg(unix)]
pub(in crate::engine::secrets) fn engine_on(fake: &fake_podman::FakePodman) -> Engine {
Engine::with_base_dir(fake.client(), "proj".to_string(), std::env::temp_dir())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::libpod::Client;
use std::path::PathBuf;
fn engine_with_base(base: &str) -> Engine {
Engine::with_base_dir(
Client::new("unused"),
"proj".to_string(),
PathBuf::from(base),
)
}
fn only_file_path(engine: &Engine, yaml: &str) -> PathBuf {
let file = crate::compose::parse_str_raw(yaml).unwrap();
let union = collect_payload_union("proj", &file, &engine.base_dir).unwrap();
assert_eq!(union.len(), 1);
match union.into_values().next().unwrap() {
Payload::File(p) => p,
Payload::Inline(_) => panic!("expected a file payload"),
}
}
#[test]
fn secret_file_relative_path_is_anchored_to_base_dir() {
let base = PathBuf::from("/srv/project");
let yaml = "services:\n web:\n image: nginx\n secrets: [tok]\nsecrets:\n tok:\n file: secret.txt\n";
let engine = engine_with_base(&base.to_string_lossy());
assert_eq!(only_file_path(&engine, yaml), base.join("secret.txt"));
}
#[cfg(unix)]
#[test]
fn config_file_absolute_path_is_passed_through() {
let yaml = "services:\n web:\n image: nginx\n configs: [cfg]\nconfigs:\n cfg:\n file: /etc/app/cfg.yaml\n";
let engine = engine_with_base("/srv/project");
assert_eq!(
only_file_path(&engine, yaml),
PathBuf::from("/etc/app/cfg.yaml")
);
}
#[test]
fn inline_union_dedups_shared_secret_across_services() {
let yaml = "services:\n a:\n image: nginx\n secrets: [tok]\n b:\n image: nginx\n secrets: [tok]\nsecrets:\n tok:\n content: shared\n";
let file = crate::compose::parse_str_raw(yaml).unwrap();
let union = collect_payload_union("proj", &file, Path::new("/base")).unwrap();
assert_eq!(union.len(), 1);
assert!(matches!(
union.get("proj_secret_tok"),
Some(Payload::Inline(b)) if b == b"shared"
));
}
#[test]
fn payload_union_collects_every_source_podup_creates_but_not_external() {
let yaml = "services:\n web:\n image: nginx\n secrets: [tok, ext, onfile]\n configs: [cfg]\nsecrets:\n tok:\n content: s\n ext:\n external: true\n onfile:\n file: ./f.txt\nconfigs:\n cfg:\n content: c\n";
let file = crate::compose::parse_str_raw(yaml).unwrap();
let union = collect_payload_union("proj", &file, Path::new("/base")).unwrap();
let mut names: Vec<&String> = union.keys().collect();
names.sort();
assert_eq!(
names,
vec!["proj_config_cfg", "proj_secret_onfile", "proj_secret_tok"]
);
}
#[test]
fn external_secret_is_never_in_the_payload_union() {
let yaml = "services:\n web:\n image: nginx\n secrets: [tok]\nsecrets:\n tok:\n external: true\n";
let file = crate::compose::parse_str_raw(yaml).unwrap();
let union = collect_payload_union("proj", &file, Path::new("/base")).unwrap();
assert!(union.is_empty());
}
}