fix(quadlet): convert leading ~ in host paths to %h
systemd does not perform shell-style tilde expansion, so a host path starting with `~/` left in a generated Quadlet file is passed to Podman as an invalid relative path and the service fails to start. Replace the leading `~` with the systemd `%h` specifier instead. Also fix the systemd specifier check in `absolute_clean_path()`, which used `Path::starts_with()` and therefore only matched a path whose first component was exactly `%`, causing `--absolute-host-paths` to prepend the resolve directory to paths such as `%h/data`. Closes: #166 Signed-off-by: DualFroz <me@dualfroz.com>
This commit is contained in:
1 parent
0632ed7659
commit
00a1dd936a
1 file changed
+56
-1
+56
-1
@@ -450,6 +450,10 @@ multiple times.";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for file in &mut files {
|
||||||
|
file.host_paths_home_specifier();
|
||||||
|
}
|
||||||
|
|
||||||
let downgrade = self.podman_version < PodmanVersion::LATEST;
|
let downgrade = self.podman_version < PodmanVersion::LATEST;
|
||||||
if resolve_dir.is_some() || self.no_start_with_pod || downgrade {
|
if resolve_dir.is_some() || self.no_start_with_pod || downgrade {
|
||||||
for file in &mut files {
|
for file in &mut files {
|
||||||
@@ -826,6 +830,15 @@ impl File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If a Quadlet file, replace a leading `~` in host paths with the systemd `%h` specifier.
|
||||||
|
fn host_paths_home_specifier(&mut self) {
|
||||||
|
for path in self.host_paths() {
|
||||||
|
if let Some(home_path) = home_specifier_path(path) {
|
||||||
|
*path = home_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// If a Quadlet file, make all host paths absolute and clean.
|
/// If a Quadlet file, make all host paths absolute and clean.
|
||||||
///
|
///
|
||||||
/// Relative paths are resolved using `resolve_dir` as the base.
|
/// Relative paths are resolved using `resolve_dir` as the base.
|
||||||
@@ -884,10 +897,20 @@ impl File {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If `path` starts with `~`, a version using the systemd `%h` specifier is returned.
|
||||||
|
///
|
||||||
|
/// systemd does not perform shell-style tilde expansion, so a `~` left in a Quadlet file results in
|
||||||
|
/// an invalid relative path being given to Podman.
|
||||||
|
fn home_specifier_path(path: &Path) -> Option<PathBuf> {
|
||||||
|
let rest = path.to_str()?.strip_prefix('~')?;
|
||||||
|
(rest.is_empty() || rest.starts_with('/')).then(|| format!("%h{rest}").into())
|
||||||
|
}
|
||||||
|
|
||||||
/// If `path` is relative, it is resolved using `resolve_dir` and a cleaned version is returned.
|
/// If `path` is relative, it is resolved using `resolve_dir` and a cleaned version is returned.
|
||||||
fn absolute_clean_path(resolve_dir: &Path, path: &Path) -> PathBuf {
|
fn absolute_clean_path(resolve_dir: &Path, path: &Path) -> PathBuf {
|
||||||
// Paths starting with "%" are also absolute because they start with a systemd specifier.
|
// Paths starting with "%" are also absolute because they start with a systemd specifier.
|
||||||
let path: Cow<Path> = if path.is_absolute() || path.starts_with("%") {
|
let specifier = path.to_str().is_some_and(|path| path.starts_with('%'));
|
||||||
|
let path: Cow<Path> = if path.is_absolute() || specifier {
|
||||||
path.into()
|
path.into()
|
||||||
} else {
|
} else {
|
||||||
resolve_dir.join(path).into()
|
resolve_dir.join(path).into()
|
||||||
@@ -999,4 +1022,36 @@ mod tests {
|
|||||||
fn verify_cli() {
|
fn verify_cli() {
|
||||||
Cli::command().debug_assert();
|
Cli::command().debug_assert();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn home_specifier_paths() {
|
||||||
|
assert_eq!(
|
||||||
|
home_specifier_path(Path::new("~/foo/bar")),
|
||||||
|
Some("%h/foo/bar".into()),
|
||||||
|
);
|
||||||
|
assert_eq!(home_specifier_path(Path::new("~")), Some("%h".into()));
|
||||||
|
assert_eq!(home_specifier_path(Path::new("~user/foo")), None);
|
||||||
|
assert_eq!(home_specifier_path(Path::new("/foo/~/bar")), None);
|
||||||
|
assert_eq!(home_specifier_path(Path::new("./foo")), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn volume_home_specifier() -> color_eyre::Result<()> {
|
||||||
|
let cli = Cli::parse_from(["podlet", "podman", "run", "-v", "~/data:/data:Z", "image"]);
|
||||||
|
let files = cli.try_into_files()?;
|
||||||
|
let file = files.first().expect("one file created");
|
||||||
|
assert!(
|
||||||
|
file.serialize(&HashSet::new())?
|
||||||
|
.contains("Volume=%h/data:/data:Z")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn specifier_path_not_resolved() {
|
||||||
|
assert_eq!(
|
||||||
|
absolute_clean_path(Path::new("/resolve/dir"), Path::new("%h/data")),
|
||||||
|
PathBuf::from("%h/data"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user