Skip to content
Snippets Groups Projects
Commit 90a71da1 authored by Martin Mareš's avatar Martin Mareš
Browse files

Improve handling of paths

Variables ending in _path contain a Path, those ending in _dir are
strings.
parent e0b1ed0f
No related branches found
No related tags found
No related merge requests found
...@@ -3,5 +3,8 @@ ...@@ -3,5 +3,8 @@
# Where to find container configuration files # Where to find container configuration files
container_config_dir = "/etc/shipcat/containers" container_config_dir = "/etc/shipcat/containers"
# Where to find container roots (can be overridden by container's config)
container_root_dir = "/aux/containers"
# Overall verbosity # Overall verbosity
verbosity = 0 verbosity = 0
...@@ -16,7 +16,8 @@ class ConfigError(RuntimeError): ...@@ -16,7 +16,8 @@ class ConfigError(RuntimeError):
class GlobalConfig: class GlobalConfig:
container_config_dir: Path container_config_path: Path
container_root_path: Path
verbosity: int verbosity: int
@classmethod @classmethod
...@@ -39,16 +40,19 @@ class GlobalConfig: ...@@ -39,16 +40,19 @@ class GlobalConfig:
def parse(self, walker: Walker) -> None: def parse(self, walker: Walker) -> None:
with walker.enter_object() as w: with walker.enter_object() as w:
self.container_config_dir = Path(w['container_config_dir'].as_str()) self.container_config_path = Path(w['container_config_dir'].as_str())
self.container_root_path = Path(w['container_root_dir'].as_str())
self.verbosity = w['verbosity'].as_int(0) self.verbosity = w['verbosity'].as_int(0)
class ContainerConfig: class ContainerConfig:
container: str name: str
root_dir: str root_path: Path
data_path: Path
image: str image: str
allowed_users: Set[int] allowed_users: Set[int]
allowed_groups: Set[int] allowed_groups: Set[int]
global_config: GlobalConfig
# Automatically generated # Automatically generated
pid_file: str pid_file: str
...@@ -59,7 +63,7 @@ class ContainerConfig: ...@@ -59,7 +63,7 @@ class ContainerConfig:
if not re.fullmatch(r'[0-9A-Za-z_-]+', name): if not re.fullmatch(r'[0-9A-Za-z_-]+', name):
raise ConfigError(f'Invalid container name {name}') raise ConfigError(f'Invalid container name {name}')
path = global_config.container_config_dir / (name + '.toml') path = global_config.container_config_path / (name + '.toml')
try: try:
with open(path, 'rb') as f: with open(path, 'rb') as f:
root = tomllib.load(f) root = tomllib.load(f)
...@@ -69,7 +73,8 @@ class ContainerConfig: ...@@ -69,7 +73,8 @@ class ContainerConfig:
raise ConfigError(f'Cannot parse container configuration {path}: {e}') raise ConfigError(f'Cannot parse container configuration {path}: {e}')
cc = ContainerConfig() cc = ContainerConfig()
cc.container = name cc.name = name
cc.global_config = global_config
try: try:
cc.parse(Walker(root)) cc.parse(Walker(root))
except WalkerError as e: except WalkerError as e:
...@@ -79,6 +84,13 @@ class ContainerConfig: ...@@ -79,6 +84,13 @@ class ContainerConfig:
def parse(self, walker: Walker) -> None: def parse(self, walker: Walker) -> None:
with walker.enter_object() as w: with walker.enter_object() as w:
rd = w['root_dir']
if rd.is_present():
self.root_path = Path(rd.as_str())
else:
self.root_path = self.global_config.container_root_path / self.name
self.data_path = self.root_path / 'data'
self.root_dir = w['root_dir'].as_str() self.root_dir = w['root_dir'].as_str()
self.image = w['image'].as_str() self.image = w['image'].as_str()
...@@ -100,5 +112,5 @@ class ContainerConfig: ...@@ -100,5 +112,5 @@ class ContainerConfig:
wu.raise_error(f'Unknown group {name}') wu.raise_error(f'Unknown group {name}')
self.allowed_groups.add(grp.gr_gid) self.allowed_groups.add(grp.gr_gid)
self.pid_file = f'/run/shc/{self.container}.pid' self.pid_file = f'/run/shc/{self.name}.pid'
self.user_name = self.container self.user_name = self.name
...@@ -145,7 +145,7 @@ def cmd_init(args: argparse.Namespace) -> None: ...@@ -145,7 +145,7 @@ def cmd_init(args: argparse.Namespace) -> None:
progress(f'Using user {cc.user_name}, uid {uid}, subuids {sur.first}+{sur.count}, subgids {sgr.first}+{sgr.count}\n') progress(f'Using user {cc.user_name}, uid {uid}, subuids {sur.first}+{sur.count}, subgids {sgr.first}+{sgr.count}\n')
root_path = Path(cc.root_dir) root_path = cc.root_path
progress(f'Container directory {root_path}: ') progress(f'Container directory {root_path}: ')
if root_path.is_dir(): if root_path.is_dir():
progress('already exists\n') progress('already exists\n')
...@@ -154,7 +154,7 @@ def cmd_init(args: argparse.Namespace) -> None: ...@@ -154,7 +154,7 @@ def cmd_init(args: argparse.Namespace) -> None:
os.chown(root_path, 0, sgr.first) os.chown(root_path, 0, sgr.first)
progress('created\n') progress('created\n')
data_path = root_path / 'data' data_path = cc.data_path
progress(f'Data directory {data_path}: ') progress(f'Data directory {data_path}: ')
if data_path.is_dir(): if data_path.is_dir():
progress('already exists\n') progress('already exists\n')
...@@ -173,35 +173,36 @@ def cmd_init(args: argparse.Namespace) -> None: ...@@ -173,35 +173,36 @@ def cmd_init(args: argparse.Namespace) -> None:
def service_action(cc: ContainerConfig, action: str) -> None: def service_action(cc: ContainerConfig, action: str) -> None:
run_command( run_command(
['systemctl', action, f'shc@{cc.container}.service'] ['systemctl', action, f'shc@{cc.name}.service']
) )
def cmd_create(args: argparse.Namespace) -> None: def cmd_create(args: argparse.Namespace) -> None:
name = args.name
cc = setup_container(args, False) cc = setup_container(args, False)
data_dir = Path(cc.root_dir) / 'data'
ip = socket.gethostbyname(name)
run_command( run_command(
['podman', 'pull', cc.image] ['podman', 'pull', cc.image]
) )
service_action(cc, 'stop') service_action(cc, 'stop')
create_container(cc)
def create_container(cc: ContainerConfig) -> None:
ip = socket.gethostbyname(cc.name)
run_command( run_command(
['podman', 'rm', '-if', name] ['podman', 'rm', '-if', cc.name]
) )
run_command( run_command(
[ [
'podman', 'create', 'podman', 'create',
'--name', name, '--name', cc.name,
'--conmon-pidfile', cc.pid_file, '--conmon-pidfile', cc.pid_file,
'--log-driver', 'journald', '--log-driver', 'journald',
'--hostname', name, '--hostname', cc.name,
'--volume', f'{data_dir}:/data', '--volume', f'{cc.data_path}:/data',
'--network', 'static', '--network', 'static',
'--ip', ip, '--ip', ip,
'--subuidname', cc.user_name, '--subuidname', cc.user_name,
...@@ -240,7 +241,7 @@ def cmd_shell(args: argparse.Namespace) -> None: ...@@ -240,7 +241,7 @@ def cmd_shell(args: argparse.Namespace) -> None:
cc = setup_container(args, False) cc = setup_container(args, False)
run_command( run_command(
['podman', 'exec', '-it', cc.container, '/bin/bash'] ['podman', 'exec', '-it', cc.name, '/bin/bash']
) )
...@@ -252,7 +253,7 @@ def cmd_exec(args: argparse.Namespace) -> None: ...@@ -252,7 +253,7 @@ def cmd_exec(args: argparse.Namespace) -> None:
cmd.append('--tty') cmd.append('--tty')
if args.user is not None: if args.user is not None:
cmd.extend(['--user', args.user]) cmd.extend(['--user', args.user])
cmd.append(cc.container) cmd.append(cc.name)
cmd.extend(args.arg) cmd.extend(args.arg)
run_command(cmd) run_command(cmd)
...@@ -282,33 +283,36 @@ def cmd_rsync(args: argparse.Namespace) -> None: ...@@ -282,33 +283,36 @@ def cmd_rsync(args: argparse.Namespace) -> None:
cmd = ['podman', 'exec', '-i'] cmd = ['podman', 'exec', '-i']
if user is not None: if user is not None:
cmd.extend(['--user', user]) cmd.extend(['--user', user])
cmd.append(cc.container) cmd.append(cc.name)
cmd.extend(rsync) cmd.extend(rsync)
run_command(cmd) run_command(cmd)
def cmd_list(args: argparse.Namespace) -> None: def cmd_list(args: argparse.Namespace) -> None:
for conf in Path(config.container_config_dir).iterdir(): for conf in config.container_config_path.iterdir():
if conf.suffix == '.toml': if conf.suffix == '.toml':
cc = ContainerConfig.load(config, conf.stem) cc = ContainerConfig.load(config, conf.stem)
if args.all or check_rights(cc, args.as_user, args.as_groups): if args.all or check_rights(cc, args.as_user, args.as_groups):
print(cc.container) print(cc.name)
def main_service_start(): def main_service_start():
# Service helper for service start, called by root # Service helper for service start, called by root
if len(sys.argv) != 2: if len(sys.argv) != 2:
die("Expected arguments: container") die("Expected arguments: name")
config = load_config() config = load_config()
cc = ContainerConfig.load(config, sys.argv[1]) cc = ContainerConfig.load(config, sys.argv[1])
pid_file = f'/run/shc/{cc.container}.pid' # Just to be sure
run_command(['podman', 'stop', '--ignore', cc.name])
pid_file = f'/run/shc/{cc.name}.pid'
Path(pid_file).unlink(missing_ok=True) Path(pid_file).unlink(missing_ok=True)
run_command(['podman', 'start', cc.container]) run_command(['podman', 'start', cc.name])
def main_service_stop(): def main_service_stop():
...@@ -320,7 +324,7 @@ def main_service_stop(): ...@@ -320,7 +324,7 @@ def main_service_stop():
config = load_config() config = load_config()
cc = ContainerConfig.load(config, sys.argv[1]) cc = ContainerConfig.load(config, sys.argv[1])
run_command(['podman', 'stop', '--ignore', cc.container]) run_command(['podman', 'stop', '--ignore', cc.name])
def parse_int_list(s: str) -> List[int]: def parse_int_list(s: str) -> List[int]:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment