wrapper: refactor docker into submodule
This commit is contained in:
parent
b6dfc9e065
commit
418a8c16c2
6 changed files with 219 additions and 121 deletions
|
@ -22,7 +22,7 @@ RUN yes | pacman -Scc
|
||||||
|
|
||||||
RUN sed -i "s/SigLevel.*/SigLevel = Never/g" /etc/pacman.conf
|
RUN sed -i "s/SigLevel.*/SigLevel = Never/g" /etc/pacman.conf
|
||||||
|
|
||||||
ENV KUPFERBOOTSTRAP_DOCKER=1
|
ENV KUPFERBOOTSTRAP_WRAPPED=DOCKER
|
||||||
ENV PATH=/app/bin:/app/local/bin:$PATH
|
ENV PATH=/app/bin:/app/local/bin:$PATH
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from constants import WRAPPER_TYPES
|
||||||
|
|
||||||
CONFIG_DIR = appdirs.user_config_dir('kupfer')
|
CONFIG_DIR = appdirs.user_config_dir('kupfer')
|
||||||
CACHE_DIR = appdirs.user_cache_dir('kupfer')
|
CACHE_DIR = appdirs.user_cache_dir('kupfer')
|
||||||
|
|
||||||
|
@ -27,6 +29,9 @@ PROFILE_DEFAULTS: Profile = {
|
||||||
PROFILE_EMPTY: Profile = {key: None for key in PROFILE_DEFAULTS.keys()}
|
PROFILE_EMPTY: Profile = {key: None for key in PROFILE_DEFAULTS.keys()}
|
||||||
|
|
||||||
CONFIG_DEFAULTS = {
|
CONFIG_DEFAULTS = {
|
||||||
|
'wrapper': {
|
||||||
|
'type': 'docker',
|
||||||
|
},
|
||||||
'build': {
|
'build': {
|
||||||
'ccache': True,
|
'ccache': True,
|
||||||
'clean_mode': True,
|
'clean_mode': True,
|
||||||
|
|
|
@ -151,3 +151,8 @@ CHROOT_PATHS = {
|
||||||
'pkgbuilds': '/pkgbuilds',
|
'pkgbuilds': '/pkgbuilds',
|
||||||
'images': '/images',
|
'images': '/images',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WRAPPER_TYPES = [
|
||||||
|
'none',
|
||||||
|
'docker',
|
||||||
|
]
|
||||||
|
|
|
@ -1,137 +1,36 @@
|
||||||
import atexit
|
|
||||||
import os
|
import os
|
||||||
import pathlib
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import uuid
|
|
||||||
import click
|
import click
|
||||||
import logging
|
import logging
|
||||||
from config import config, dump_file as dump_config_file
|
|
||||||
from constants import CHROOT_PATHS
|
from config import config
|
||||||
from utils import programs_available
|
from utils import programs_available
|
||||||
|
from .docker import DockerWrapper
|
||||||
|
|
||||||
DOCKER_PATHS = CHROOT_PATHS.copy()
|
wrapper_impls = {
|
||||||
|
'docker': DockerWrapper,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def wrap_docker():
|
def get_wrapper_type(wrapper_type: str = None):
|
||||||
|
return wrapper_type or config.file['wrapper']['type']
|
||||||
|
|
||||||
def _docker_volumes(volume_mappings: dict[str, str]) -> list[str]:
|
|
||||||
result = []
|
|
||||||
for source, destination in volume_mappings.items():
|
|
||||||
result += ['-v', f'{source}:{destination}:z']
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _filter_args(args):
|
def wrap(wrapper_type: str = None):
|
||||||
"""hack. filter out --config since it doesn't apply in docker"""
|
wrapper_type = get_wrapper_type(wrapper_type)
|
||||||
results = []
|
if wrapper_type != 'none':
|
||||||
done = False
|
wrapper_impls[wrapper_type]().wrap()
|
||||||
for i, arg in enumerate(args):
|
|
||||||
if done:
|
|
||||||
break
|
|
||||||
if arg[0] != '-':
|
|
||||||
results += args[i:]
|
|
||||||
done = True
|
|
||||||
break
|
|
||||||
for argname in ['--config', '-C']:
|
|
||||||
if arg.startswith(argname):
|
|
||||||
done = True
|
|
||||||
if arg.strip() != argname: # arg is longer, assume --arg=value
|
|
||||||
offset = 1
|
|
||||||
else:
|
|
||||||
offset = 2
|
|
||||||
results += args[i + offset:]
|
|
||||||
break
|
|
||||||
if not done:
|
|
||||||
results.append(arg)
|
|
||||||
return results
|
|
||||||
|
|
||||||
script_path = config.runtime['script_source_dir']
|
|
||||||
with open(os.path.join(script_path, 'version.txt')) as version_file:
|
|
||||||
version = version_file.read().replace('\n', '')
|
|
||||||
tag = f'registry.gitlab.com/kupfer/kupferbootstrap:{version}'
|
|
||||||
if version == 'dev':
|
|
||||||
logging.info(f'Building docker image "{tag}"')
|
|
||||||
cmd = [
|
|
||||||
'docker',
|
|
||||||
'build',
|
|
||||||
'.',
|
|
||||||
'-t',
|
|
||||||
tag,
|
|
||||||
] + (['-q'] if not config.runtime['verbose'] else [])
|
|
||||||
logging.debug('Running docker cmd: ' + ' '.join(cmd))
|
|
||||||
result = subprocess.run(cmd, cwd=script_path, capture_output=True)
|
|
||||||
if result.returncode != 0:
|
|
||||||
logging.fatal('Failed to build docker image:\n' + result.stderr.decode())
|
|
||||||
exit(1)
|
|
||||||
else:
|
|
||||||
# Check if the image for the version already exists
|
|
||||||
result = subprocess.run(
|
|
||||||
[
|
|
||||||
'docker',
|
|
||||||
'images',
|
|
||||||
'-q',
|
|
||||||
tag,
|
|
||||||
],
|
|
||||||
capture_output=True,
|
|
||||||
)
|
|
||||||
if result.stdout == b'':
|
|
||||||
logging.info(f'Pulling kupferbootstrap docker image version \'{version}\'')
|
|
||||||
subprocess.run([
|
|
||||||
'docker',
|
|
||||||
'pull',
|
|
||||||
tag,
|
|
||||||
])
|
|
||||||
container_name = f'kupferbootstrap-{str(uuid.uuid4())}'
|
|
||||||
wrapped_config = f'/tmp/kupfer/{container_name}_wrapped.toml'
|
|
||||||
|
|
||||||
def at_exit():
|
def is_wrapped(wrapper_type: str = None):
|
||||||
subprocess.run(
|
wrapper_type = get_wrapper_type(wrapper_type)
|
||||||
[
|
return os.getenv('KUPFERBOOTSTRAP_WRAPPED') == wrapper_type.capitalize()
|
||||||
'docker',
|
|
||||||
'kill',
|
|
||||||
container_name,
|
|
||||||
],
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
os.remove(wrapped_config)
|
|
||||||
|
|
||||||
atexit.register(at_exit)
|
|
||||||
|
|
||||||
dump_config_file(file_path=wrapped_config, config=(config.file | {'paths': DOCKER_PATHS}))
|
|
||||||
ssh_dir = os.path.join(pathlib.Path.home(), '.ssh')
|
|
||||||
if not os.path.exists(ssh_dir):
|
|
||||||
os.makedirs(ssh_dir)
|
|
||||||
volumes = {
|
|
||||||
'/dev': '/dev',
|
|
||||||
os.getcwd(): '/src',
|
|
||||||
wrapped_config: '/root/.config/kupfer/kupferbootstrap.toml',
|
|
||||||
ssh_dir: '/root/.ssh',
|
|
||||||
}
|
|
||||||
volumes |= dict({config.get_path(vol_name): vol_dest for vol_name, vol_dest in DOCKER_PATHS.items()})
|
|
||||||
docker_cmd = [
|
|
||||||
'docker',
|
|
||||||
'run',
|
|
||||||
'--name',
|
|
||||||
container_name,
|
|
||||||
'--rm',
|
|
||||||
'--interactive',
|
|
||||||
'--tty',
|
|
||||||
'--privileged',
|
|
||||||
] + _docker_volumes(volumes) + [tag]
|
|
||||||
|
|
||||||
kupfer_cmd = ['kupferbootstrap', '--config', '/root/.config/kupfer/kupferbootstrap.toml'] + _filter_args(sys.argv[1:])
|
|
||||||
|
|
||||||
cmd = docker_cmd + kupfer_cmd
|
|
||||||
logging.debug('Wrapping in docker:' + repr(cmd))
|
|
||||||
result = subprocess.run(cmd)
|
|
||||||
|
|
||||||
exit(result.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def enforce_wrap(no_wrapper=False):
|
def enforce_wrap(no_wrapper=False):
|
||||||
if os.getenv('KUPFERBOOTSTRAP_DOCKER') != '1' and not config.runtime['no_wrap'] and not no_wrapper:
|
wrapper_type = get_wrapper_type()
|
||||||
wrap_docker()
|
if wrapper_type != 'none' and not is_wrapped(wrapper_type) and not config.runtime['no_wrap'] and not no_wrapper:
|
||||||
|
logging.info(f'Wrapping in {wrapper_type}')
|
||||||
|
wrap()
|
||||||
|
|
||||||
|
|
||||||
def check_programs_wrap(programs):
|
def check_programs_wrap(programs):
|
||||||
|
|
101
wrapper/docker.py
Normal file
101
wrapper/docker.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from config import config
|
||||||
|
from constants import CHROOT_PATHS
|
||||||
|
from .wrapper import BaseWrapper
|
||||||
|
|
||||||
|
DOCKER_PATHS = CHROOT_PATHS.copy()
|
||||||
|
|
||||||
|
|
||||||
|
def docker_volumes_args(volume_mappings: dict[str, str]) -> list[str]:
|
||||||
|
result = []
|
||||||
|
for source, destination in volume_mappings.items():
|
||||||
|
result += ['-v', f'{source}:{destination}:z']
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class DockerWrapper(BaseWrapper):
|
||||||
|
type = 'docker'
|
||||||
|
|
||||||
|
def wrap(self):
|
||||||
|
script_path = config.runtime['script_source_dir']
|
||||||
|
with open(os.path.join(script_path, 'version.txt')) as version_file:
|
||||||
|
version = version_file.read().replace('\n', '')
|
||||||
|
tag = f'registry.gitlab.com/kupfer/kupferbootstrap:{version}'
|
||||||
|
if version == 'dev':
|
||||||
|
logging.info(f'Building docker image "{tag}"')
|
||||||
|
cmd = [
|
||||||
|
'docker',
|
||||||
|
'build',
|
||||||
|
'.',
|
||||||
|
'-t',
|
||||||
|
tag,
|
||||||
|
] + (['-q'] if not config.runtime['verbose'] else [])
|
||||||
|
logging.debug('Running docker cmd: ' + ' '.join(cmd))
|
||||||
|
result = subprocess.run(cmd, cwd=script_path, capture_output=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.fatal('Failed to build docker image:\n' + result.stderr.decode())
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
# Check if the image for the version already exists
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
'docker',
|
||||||
|
'images',
|
||||||
|
'-q',
|
||||||
|
tag,
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if result.stdout == b'':
|
||||||
|
logging.info(f'Pulling kupferbootstrap docker image version \'{version}\'')
|
||||||
|
subprocess.run([
|
||||||
|
'docker',
|
||||||
|
'pull',
|
||||||
|
tag,
|
||||||
|
])
|
||||||
|
container_name = f'kupferbootstrap-{self.uuid}'
|
||||||
|
|
||||||
|
wrapped_config = self.generate_wrapper_config()
|
||||||
|
|
||||||
|
ssh_dir = os.path.join(pathlib.Path.home(), '.ssh')
|
||||||
|
if not os.path.exists(ssh_dir):
|
||||||
|
os.makedirs(ssh_dir)
|
||||||
|
volumes = self.get_bind_mounts_default()
|
||||||
|
volumes |= dict({config.get_path(vol_name): vol_dest for vol_name, vol_dest in DOCKER_PATHS.items()})
|
||||||
|
docker_cmd = [
|
||||||
|
'docker',
|
||||||
|
'run',
|
||||||
|
'--name',
|
||||||
|
container_name,
|
||||||
|
'--rm',
|
||||||
|
'--interactive',
|
||||||
|
'--tty',
|
||||||
|
'--privileged',
|
||||||
|
] + docker_volumes_args(volumes) + [tag]
|
||||||
|
|
||||||
|
kupfer_cmd = ['kupferbootstrap', '--config', '/root/.config/kupfer/kupferbootstrap.toml'] + self.filter_args_wrapper(sys.argv[1:])
|
||||||
|
|
||||||
|
cmd = docker_cmd + kupfer_cmd
|
||||||
|
logging.debug('Wrapping in docker:' + repr(cmd))
|
||||||
|
result = subprocess.run(cmd)
|
||||||
|
|
||||||
|
exit(result.returncode)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
'docker',
|
||||||
|
'kill',
|
||||||
|
self.identifier,
|
||||||
|
],
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
wrapper = DockerWrapper()
|
88
wrapper/wrapper.py
Normal file
88
wrapper/wrapper.py
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import atexit
|
||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
from config import config, dump_file as dump_config_file
|
||||||
|
from constants import CHROOT_PATHS
|
||||||
|
|
||||||
|
|
||||||
|
class BaseWrapper:
|
||||||
|
id = None
|
||||||
|
identifier = None
|
||||||
|
type = None
|
||||||
|
wrapped_config_path = None
|
||||||
|
|
||||||
|
def __init__(self, random_id: str = None, name: str = None):
|
||||||
|
self.uuid = str(random_id or uuid.uuid4())
|
||||||
|
self.identifier = name or f'kupferbootstrap-{self.uuid}'
|
||||||
|
|
||||||
|
def filter_args_wrapper(self, args):
|
||||||
|
"""filter out -c/--config since it doesn't apply in wrapper"""
|
||||||
|
results = []
|
||||||
|
done = False
|
||||||
|
for i, arg in enumerate(args):
|
||||||
|
if done:
|
||||||
|
break
|
||||||
|
if arg[0] != '-':
|
||||||
|
results += args[i:]
|
||||||
|
done = True
|
||||||
|
break
|
||||||
|
for argname in ['--config', '-C']:
|
||||||
|
if arg.startswith(argname):
|
||||||
|
done = True
|
||||||
|
if arg.strip() != argname: # arg is longer, assume --arg=value
|
||||||
|
offset = 1
|
||||||
|
else:
|
||||||
|
offset = 2
|
||||||
|
results += args[i + offset:]
|
||||||
|
break
|
||||||
|
if not done:
|
||||||
|
results.append(arg)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def generate_wrapper_config(
|
||||||
|
self,
|
||||||
|
target_path: str = '/tmp/kupferbootstrap',
|
||||||
|
paths: dict[str, str] = CHROOT_PATHS,
|
||||||
|
config_overrides: dict[str, dict] = {},
|
||||||
|
) -> str:
|
||||||
|
wrapped_config = f'{target_path.rstrip("/")}/{self.identifier}_wrapped.toml'
|
||||||
|
|
||||||
|
def at_exit():
|
||||||
|
self.stop()
|
||||||
|
os.remove(wrapped_config)
|
||||||
|
|
||||||
|
atexit.register(at_exit)
|
||||||
|
|
||||||
|
dump_config_file(
|
||||||
|
file_path=wrapped_config,
|
||||||
|
config=(config.file | {
|
||||||
|
'paths': paths,
|
||||||
|
'wrapper': {
|
||||||
|
'type': 'none'
|
||||||
|
}
|
||||||
|
} | config_overrides),
|
||||||
|
)
|
||||||
|
self.wrapped_config_path = wrapped_config
|
||||||
|
return wrapped_config
|
||||||
|
|
||||||
|
def wrap(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_bind_mounts_default(self, wrapped_config_path: str = None, ssh_dir: str = None, target_home: str = '/root'):
|
||||||
|
wrapped_config_path = wrapped_config_path or self.wrapped_config_path
|
||||||
|
ssh_dir = ssh_dir or os.path.join(pathlib.Path.home(), '.ssh')
|
||||||
|
assert (wrapped_config_path)
|
||||||
|
mounts = {
|
||||||
|
'/dev': '/dev',
|
||||||
|
wrapped_config_path: f'{target_home}/.config/kupfer/kupferbootstrap.toml',
|
||||||
|
}
|
||||||
|
if ssh_dir:
|
||||||
|
mounts |= {
|
||||||
|
ssh_dir: f'{target_home}/.ssh',
|
||||||
|
}
|
||||||
|
return mounts
|
Loading…
Add table
Add a link
Reference in a new issue