debian-forge/mounts/org.osbuild.bind
Michael Vogt a4dfd2614f mounts: implement new org.osbuild.bind mount
This adds a new `org.osbuild.bind` mount feature to the osbuild
mount modules. This allows to (r)bind mount parts of another mount
into the tree (or replace the default tree for a stage entirely).

The use case is the `bootc install to-filesystem` where we get
a populated disk and need to do customizations directly there
without going through an intermediate tree.

Note that right now only "--rbind" is supported and used but
we could trivially change that to become an option in either
direction. Given that the main use-case right now is to be
paried with `org.osbuild.ostree.deployment` and here the
`rbind` is crucial I would leave that the default.

Here is an example what this looks like:
```json
        {
          "type": "org.osbuild.users",
          "options": {
            "users": {
              "alice": {
                "home": "/home/alice",
                "groups": [
                  "wheel"
                ],
                "password": "$6$NV3P7UzUqP3xb1ML$3qnHpWs037VRTaOc.kirQ4.RwNz4gu9dkhAhpBYVCkHw8CMhpBKnegyyqw0QfURowarZnRnQi.jo4JEzIOvPO/",
                "key": "ssh-rsa AAA ... user@email.com"
              }
            }
          },
          "devices": {
            "disk": {
              "type": "org.osbuild.loopback",
              "options": {
                "filename": "disk.raw",
                "partscan": true
              }
            }
          },
          "mounts": [
            {
              "name": "part4",
              "type": "org.osbuild.ext4",
              "source": "disk",
              "target": "/",
              "partition": 4
            },
            ...
            {
              "name": "ostree.deployment",
              "type": "org.osbuild.ostree.deployment",
              "options": {
                "source": "mount",
                "deployment": {
                  "default": true
                }
              }
            },
            {
              "name": "bind",
              "type": "org.osbuild.bind",
	      "target": "tree://",
	      "options": {
		"source": "mount://"
	      }
            }
          ]
        },
```
2024-04-11 17:40:21 +02:00

86 lines
2.1 KiB
Python
Executable file

#!/usr/bin/python3
"""
Bind mount service
Can (r)bind mount mounts to the tree.
"""
import os.path
import subprocess
import sys
from typing import Dict
from urllib.parse import urlparse
from osbuild import mounts
SCHEMA_2 = """
"additionalProperties": false,
"required": ["name", "type", "target"],
"properties": {
"name": { "type": "string" },
"type": { "type": "string" },
"target": {
"type": "string",
"pattern": "^tree://"
},
"options": {
"required": ["source"],
"source": {
"type": "string",
"pattern": "^mount://"
}
}
}
"""
def parse_location(location, tree, mountroot: str) -> str:
# we cannot use "osutil.util.parsing" here because it is too
# tightly coupled with how arguments for stages are passed
url = urlparse(location)
path = url.netloc
if url.scheme == "tree":
return os.path.join(tree, path.rstrip("/"))
if url.scheme == "mount":
return os.path.join(mountroot, path.rstrip("/"))
raise ValueError(f"unsupported schema {url.scheme} for {location}")
class BindMount(mounts.MountService):
def __init__(self, args):
super().__init__(args)
self.mountpoint = ""
def mount(self, args: Dict):
tree = args["tree"]
mountroot = args["root"]
target = args["target"]
# we cannot use args["sources"] here because the osbuild code makes
# many assumptions about that it must link back to a "Device" so
# we follow the pattern from org.osbuild.ostree.deployment here
# and put it into "options"
options = args["options"]
source = parse_location(options.get("source"), tree, mountroot)
self.mountpoint = parse_location(target, tree, mountroot)
subprocess.run([
"mount",
"--rbind", source, self.mountpoint,
], check=True)
def umount(self):
if self.mountpoint:
subprocess.run(["umount", "-R", "-v", self.mountpoint], check=True)
self.mountpoint = ""
def sync(self):
pass
def main():
service = BindMount.from_args(sys.argv[1:])
service.main()
if __name__ == '__main__':
main()