selfprivacy-rest-api/selfprivacy_api/services/owned_path.py

104 lines
3.0 KiB
Python

from __future__ import annotations
import subprocess
import pathlib
from pydantic import BaseModel
from selfprivacy_api.utils.block_devices import BlockDevice, BlockDevices
class BindError(Exception):
pass
# May be deprecated because of Binds
class OwnedPath(BaseModel):
path: str
owner: str
group: str
class Bind:
"""
A directory that resides on some volume but we mount it into fs
where we need it.
Used for service data.
"""
def __init__(self, binding_path: str, owner: str, group: str, drive: BlockDevice):
self.binding_path = binding_path
self.owner = owner
self.group = group
self.drive = drive
# TODO: make Service return a list of binds instead of owned paths
@staticmethod
def from_owned_path(path: OwnedPath, drive_name: str) -> Bind:
drive = BlockDevices().get_block_device(drive_name)
if drive is None:
raise BindError(f"No such drive: {drive_name}")
return Bind(
binding_path=path.path, owner=path.owner, group=path.group, drive=drive
)
def bind_foldername(self) -> str:
return self.binding_path.split("/")[-1]
def location_at_volume(self) -> str:
return f"/volumes/{self.drive.name}/{self.bind_foldername()}"
def validate(self) -> str:
path = pathlib.Path(self.location_at_volume())
if not path.exists():
raise BindError(f"directory {path} is not found.")
if not path.is_dir():
raise BindError(f"{path} is not a directory.")
if path.owner() != self.owner:
raise BindError(f"{path} is not owned by {self.owner}.")
def bind(self) -> None:
try:
subprocess.run(
[
"mount",
"--bind",
self.location_at_volume(),
self.binding_path,
],
check=True,
)
except subprocess.CalledProcessError as error:
print(error.output)
raise BindError(f"Unable to mount new volume:{error.output}")
def unbind(self) -> None:
try:
subprocess.run(
["umount", self.binding_path],
check=True,
)
except subprocess.CalledProcessError:
raise BindError(f"Unable to unmount folder {self.binding_path}.")
pass
def ensure_ownership(self) -> None:
true_location = self.location_at_volume()
try:
subprocess.run(
[
"chown",
"-R",
f"{self.owner}:{self.group}",
# Could we just chown the binded location instead?
true_location,
],
check=True,
)
except subprocess.CalledProcessError as error:
print(error.output)
error_message = (
f"Unable to set ownership of {true_location} :{error.output}"
)
raise BindError(error_message)