util/path: add join_abs() to join potentially absolute paths

It turned out that in many cases, stages need to join two absolute
paths, the pipeline tree path and the path on a booted system. However,
the standard `os.path.join()` function can't handle such situation as
just prepending the root to the subsequent paths.

Add a new helper function, which is able to join any paths together,
regardless if any of them is absolute or not. If the root is not
absolute, the result will be made absolute to the filesystem root `/`.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2024-12-19 14:14:12 +01:00 committed by Brian C. Lane
parent 67d9663c83
commit 8463394d2c
2 changed files with 54 additions and 1 deletions

View file

@ -2,7 +2,7 @@
import errno
import os
import os.path
from typing import Optional
from typing import Optional, Union
from .ctx import suppress_oserror
@ -41,3 +41,18 @@ def in_tree(path: str, tree: str, must_exist: bool = False) -> bool:
if path.startswith(tree):
return not must_exist or os.path.exists(path)
return False
def join_abs(root: Union[str, os.PathLike], *paths: Union[str, os.PathLike]) -> str:
"""
Join root and paths together, handling the case where paths are absolute paths.
In that case, paths are just appended to root as if they were relative paths.
The result is always an absolute path relative to the filesystem root '/'.
"""
final_path = root
for path in paths:
if os.path.isabs(path):
final_path = os.path.join(final_path, os.path.relpath(path, os.sep))
else:
final_path = os.path.join(final_path, path)
return os.path.normpath(os.path.join(os.sep, final_path))

View file

@ -68,3 +68,41 @@ def test_in_tree():
for args, expected in cases.items():
assert path.in_tree(*args) == expected, args
@pytest.mark.parametrize("test_case", (
{
"args": ("",),
"expected": "/"
},
{
"args": ("", "file"),
"expected": "/file"
},
{
"args": ("", "/file"),
"expected": "/file"
},
{
"args": ("/tmp", "/file"),
"expected": "/tmp/file"
},
{
"args": ("/tmp", "/foo", "/bar"),
"expected": "/tmp/foo/bar"
},
{
"args": ("/", "/foo"),
"expected": "/foo"
},
{
"args": ("/", "/foo", "/bar"),
"expected": "/foo/bar"
},
{
"args": (Path("/tmp"), "/foo", "/bar"),
"expected": "/tmp/foo/bar"
},
))
def test_join_abs(test_case):
assert path.join_abs(*test_case["args"]) == test_case["expected"]