org.osbuild.mkdir: support creating dirs on mounts

This allows creating new directories on mounts:
```
- type: org.osbuild.mkdir
  options:
    paths:
      - path: mount:///boot/efi
  devices:
    disk: ...
  mounts:
    - name: boot
      target: /boot
      ...
```
This commit is contained in:
Nikita Dubrovskii 2024-09-30 15:46:31 +02:00 committed by Achilleas Koutsou
parent ad7c646712
commit 23f01307b2
3 changed files with 119 additions and 13 deletions

View file

@ -3,23 +3,26 @@ import os
import sys import sys
import osbuild.api import osbuild.api
from osbuild.util.path import in_tree from osbuild.util import parsing
def main(tree, options): def main(args):
options = args["options"]
for item in options["paths"]: for item in options["paths"]:
path = item["path"] path = item["path"]
mode = item.get("mode", 0o777) mode = item.get("mode", 0o777)
parents = item.get("parents", False) parents = item.get("parents", False)
exist_ok = item.get("exist_ok", False) exist_ok = item.get("exist_ok", False)
if not path.startswith("/"): if "://" not in path:
print("WARNING: relative path used, this is discouraged!") if not path.startswith("/"):
print("WARNING: relative path used, this is discouraged!")
target = os.path.join(tree, path.lstrip("/")) path = f"tree:///{path}"
if not in_tree(target, tree): else:
raise ValueError(f"path {path} not in tree") path = f"tree://{path}"
target = parsing.parse_location(path, args)
if parents: if parents:
os.makedirs(target, mode=mode, exist_ok=exist_ok) os.makedirs(target, mode=mode, exist_ok=exist_ok)
else: else:
@ -33,5 +36,4 @@ def main(tree, options):
if __name__ == "__main__": if __name__ == "__main__":
args = osbuild.api.arguments() sys.exit(main(osbuild.api.arguments()))
sys.exit(main(args["tree"], args["options"]))

View file

@ -1,5 +1,5 @@
{ {
"summary": "Create directories within the tree.", "summary": "Create directories within the tree or mount.",
"description": [ "description": [
"Can create one or more directories, optionally also the", "Can create one or more directories, optionally also the",
"intermediate directories. The stage can gracefully handle", "intermediate directories. The stage can gracefully handle",
@ -31,8 +31,23 @@
], ],
"properties": { "properties": {
"path": { "path": {
"type": "string", "anyOf": [
"pattern": "^\\/?(?!\\.\\.)((?!\\/\\.\\.\\/).)+$" {
"type": "string",
"description": "Target path, if a tree",
"pattern": "^\\/?(?!\\.\\.)((?!\\/\\.\\.\\/).)+$"
},
{
"type": "string",
"description": "Target path, if a mount",
"pattern": "^mount://.+"
},
{
"type": "string",
"description": "Target path, if a tree",
"pattern": "^tree://.+"
}
]
}, },
"mode": { "mode": {
"type": "number", "type": "number",

89
stages/test/test_mkdir.py Normal file
View file

@ -0,0 +1,89 @@
#!/usr/bin/python3
import contextlib
import os
import subprocess
import pytest # type: ignore
from osbuild.testutil import has_executable
STAGE_NAME = "org.osbuild.mkdir"
def test_mkdir(tmp_path, stage_module):
tree = tmp_path / "tree"
tree.mkdir()
options = {
"paths": [
{"path": "/fake_dir"},
{"path": "fake_relative_dir"}
]
}
args = {
"tree": f"{tree}",
"options": options
}
stage_module.main(args)
assert (tree / "fake_dir").exists()
assert (tree / "fake_relative_dir").exists()
def test_mkdir_on_a_tree(tmp_path, stage_module):
tree = tmp_path / "tree"
tree.mkdir()
options = {
"paths": [
{
"path": "tree:///fake_parent/fake_dir",
"parents": 1
}
]
}
args = {
"tree": f"{tree}",
"options": options
}
stage_module.main(args)
assert (tree / "fake_parent/fake_dir").exists()
@pytest.mark.skipif(os.getuid() != 0, reason="needs root")
@pytest.mark.skipif(not has_executable("mkfs.ext4"), reason="need mkfs.ext4")
def test_mkdir_on_a_mount(tmp_path, stage_module):
tree = tmp_path / "tree"
tree.mkdir()
# Create fake EXT4 disk image
fake_disk_path = tmp_path / "fake.img"
with fake_disk_path.open("w") as fp:
fp.truncate(10 * 1024 * 1024)
subprocess.run(
["mkfs.ext4", os.fspath(fake_disk_path)], check=True)
fake_disk_mnt = tmp_path / "mounts"
fake_disk_mnt.mkdir()
with contextlib.ExitStack() as cm:
subprocess.run(["mount", fake_disk_path, fake_disk_mnt], check=True)
cm.callback(subprocess.run, ["umount", fake_disk_mnt], check=True)
options = {
"paths": [
{
"path": "mount:///fake_parent/fake_dir",
"parents": 1
}
]
}
args = {
"tree": f"{tree}",
"options": options,
"paths": {
"mounts": fake_disk_mnt,
}
}
stage_module.main(args)
assert (fake_disk_mnt / "fake_parent/fake_dir").exists()