Compare commits
8 commits
dev
...
prawn/prof
Author | SHA1 | Date | |
---|---|---|---|
|
2408f00132 | ||
|
bc3fa84f58 | ||
|
a86f2d3cbb | ||
|
01dc62aa92 | ||
|
da1c3b22b3 | ||
|
07d084fd76 | ||
|
2c1584ab12 | ||
|
128b1b7e5e |
5 changed files with 74 additions and 13 deletions
|
@ -7,6 +7,7 @@ from typing import Any, Iterable, Optional, Union
|
||||||
|
|
||||||
from devices.device import get_devices
|
from devices.device import get_devices
|
||||||
from flavours.flavour import get_flavours
|
from flavours.flavour import get_flavours
|
||||||
|
from wrapper import execute_without_exit
|
||||||
|
|
||||||
from .scheme import Profile
|
from .scheme import Profile
|
||||||
from .profile import PROFILE_EMPTY, PROFILE_DEFAULTS
|
from .profile import PROFILE_EMPTY, PROFILE_DEFAULTS
|
||||||
|
@ -132,16 +133,22 @@ def prompt_choice(current: Optional[Any], key: str, choices: Iterable[Any], allo
|
||||||
|
|
||||||
|
|
||||||
def prompt_profile_device(current: Optional[str], profile_name: str) -> tuple[str, bool]:
|
def prompt_profile_device(current: Optional[str], profile_name: str) -> tuple[str, bool]:
|
||||||
devices = get_devices()
|
|
||||||
print(click.style("Pick your device!\nThese are the available devices:", bold=True))
|
print(click.style("Pick your device!\nThese are the available devices:", bold=True))
|
||||||
|
devices = execute_without_exit(get_devices, ['devices'])
|
||||||
|
if devices is None:
|
||||||
|
print("(wrapper mode, input for this field will not be checked for correctness)")
|
||||||
|
return prompt_config(text=f'{profile_name}.device', default=current)
|
||||||
for dev in sorted(devices.keys()):
|
for dev in sorted(devices.keys()):
|
||||||
print(devices[dev])
|
print(devices[dev])
|
||||||
return prompt_choice(current, f'profiles.{profile_name}.device', devices.keys())
|
return prompt_choice(current, f'profiles.{profile_name}.device', devices.keys())
|
||||||
|
|
||||||
|
|
||||||
def prompt_profile_flavour(current: Optional[str], profile_name: str) -> tuple[str, bool]:
|
def prompt_profile_flavour(current: Optional[str], profile_name: str) -> tuple[str, bool]:
|
||||||
flavours = get_flavours()
|
|
||||||
print(click.style("Pick your flavour!\nThese are the available flavours:", bold=True))
|
print(click.style("Pick your flavour!\nThese are the available flavours:", bold=True))
|
||||||
|
flavours = execute_without_exit(get_flavours, ['flavours'])
|
||||||
|
if flavours is None:
|
||||||
|
print("(wrapper mode, input for this field will not be checked for correctness)")
|
||||||
|
return prompt_config(text=f'{profile_name}.flavour', default=current)
|
||||||
for f in sorted(flavours.keys()):
|
for f in sorted(flavours.keys()):
|
||||||
print(flavours[f])
|
print(flavours[f])
|
||||||
return prompt_choice(current, f'profiles.{profile_name}.flavour', flavours.keys())
|
return prompt_choice(current, f'profiles.{profile_name}.flavour', flavours.keys())
|
||||||
|
@ -176,7 +183,12 @@ def prompt_for_save(retry_ctx: Optional[click.Context] = None):
|
||||||
If `retry_ctx` is passed, the context's command will be reexecuted with the same arguments if the user chooses to retry.
|
If `retry_ctx` is passed, the context's command will be reexecuted with the same arguments if the user chooses to retry.
|
||||||
False will still be returned as the retry is expected to either save, perform another retry or arbort.
|
False will still be returned as the retry is expected to either save, perform another retry or arbort.
|
||||||
"""
|
"""
|
||||||
|
from wrapper import is_wrapped
|
||||||
if click.confirm(f'Do you want to save your changes to {config.runtime.config_file}?', default=True):
|
if click.confirm(f'Do you want to save your changes to {config.runtime.config_file}?', default=True):
|
||||||
|
if is_wrapped():
|
||||||
|
logging.warning("Writing to config file inside wrapper."
|
||||||
|
"This is pointless and probably a bug."
|
||||||
|
"Your host config file will not be modified.")
|
||||||
return True
|
return True
|
||||||
if retry_ctx:
|
if retry_ctx:
|
||||||
if click.confirm('Retry? ("n" to quit without saving)', default=True):
|
if click.confirm('Retry? ("n" to quit without saving)', default=True):
|
||||||
|
@ -201,6 +213,8 @@ noninteractive_flag = click.option('-N', '--non-interactive', is_flag=True)
|
||||||
noop_flag = click.option('--noop', '-n', help="Don't write changes to file", is_flag=True)
|
noop_flag = click.option('--noop', '-n', help="Don't write changes to file", is_flag=True)
|
||||||
noparse_flag = click.option('--no-parse', help="Don't search PKGBUILDs for devices and flavours", is_flag=True)
|
noparse_flag = click.option('--no-parse', help="Don't search PKGBUILDs for devices and flavours", is_flag=True)
|
||||||
|
|
||||||
|
CONFIG_MSG = ("Leave fields empty to leave them at their currently displayed value.")
|
||||||
|
|
||||||
|
|
||||||
@cmd_config.command(name='init')
|
@cmd_config.command(name='init')
|
||||||
@noninteractive_flag
|
@noninteractive_flag
|
||||||
|
@ -224,6 +238,7 @@ def cmd_config_init(
|
||||||
):
|
):
|
||||||
"""Initialize the config file"""
|
"""Initialize the config file"""
|
||||||
if not non_interactive:
|
if not non_interactive:
|
||||||
|
logging.info(CONFIG_MSG)
|
||||||
results: dict[str, dict] = {}
|
results: dict[str, dict] = {}
|
||||||
for section in sections:
|
for section in sections:
|
||||||
if section not in CONFIG_SECTIONS:
|
if section not in CONFIG_SECTIONS:
|
||||||
|
@ -239,7 +254,14 @@ def cmd_config_init(
|
||||||
results[section][key] = result
|
results[section][key] = result
|
||||||
|
|
||||||
config.update(results)
|
config.update(results)
|
||||||
|
print("Main configuration complete")
|
||||||
|
if not noop:
|
||||||
|
if prompt_for_save(ctx):
|
||||||
|
config.write()
|
||||||
|
else:
|
||||||
|
return
|
||||||
if 'profiles' in sections:
|
if 'profiles' in sections:
|
||||||
|
print("Configuring profiles")
|
||||||
current_profile = 'default' if 'current' not in config.file.profiles else config.file.profiles.current
|
current_profile = 'default' if 'current' not in config.file.profiles else config.file.profiles.current
|
||||||
new_current, _ = prompt_config('profiles.current', default=current_profile, field_type=str)
|
new_current, _ = prompt_config('profiles.current', default=current_profile, field_type=str)
|
||||||
profile, changed = prompt_profile(new_current, create=True, no_parse=no_parse)
|
profile, changed = prompt_profile(new_current, create=True, no_parse=no_parse)
|
||||||
|
@ -266,6 +288,7 @@ def cmd_config_set(ctx, key_vals: list[str], non_interactive: bool = False, noop
|
||||||
like `build.clean_mode=false` or alternatively just keys to get prompted if run interactively.
|
like `build.clean_mode=false` or alternatively just keys to get prompted if run interactively.
|
||||||
"""
|
"""
|
||||||
config.enforce_config_loaded()
|
config.enforce_config_loaded()
|
||||||
|
logging.info(CONFIG_MSG)
|
||||||
config_copy = deepcopy(config.file)
|
config_copy = deepcopy(config.file)
|
||||||
for pair in key_vals:
|
for pair in key_vals:
|
||||||
split_pair = pair.split('=')
|
split_pair = pair.split('=')
|
||||||
|
@ -323,6 +346,7 @@ def cmd_profile_init(ctx, name: Optional[str] = None, non_interactive: bool = Fa
|
||||||
profile = deepcopy(PROFILE_EMPTY)
|
profile = deepcopy(PROFILE_EMPTY)
|
||||||
if name == 'current':
|
if name == 'current':
|
||||||
raise Exception("profile name 'current' not allowed")
|
raise Exception("profile name 'current' not allowed")
|
||||||
|
logging.info(CONFIG_MSG)
|
||||||
name = name or config.file.profiles.current
|
name = name or config.file.profiles.current
|
||||||
if name in config.file.profiles:
|
if name in config.file.profiles:
|
||||||
profile |= config.file.profiles[name]
|
profile |= config.file.profiles[name]
|
||||||
|
@ -333,7 +357,9 @@ def cmd_profile_init(ctx, name: Optional[str] = None, non_interactive: bool = Fa
|
||||||
config.update_profile(name, profile)
|
config.update_profile(name, profile)
|
||||||
if not noop:
|
if not noop:
|
||||||
if not prompt_for_save(ctx):
|
if not prompt_for_save(ctx):
|
||||||
|
logging.info("Not saving.")
|
||||||
return
|
return
|
||||||
|
|
||||||
config.write()
|
config.write()
|
||||||
else:
|
else:
|
||||||
logging.info(f'--noop passed, not writing to {config.runtime.config_file}!')
|
logging.info(f'--noop passed, not writing to {config.runtime.config_file}!')
|
||||||
|
|
|
@ -255,7 +255,7 @@ class ConfigStateHolder:
|
||||||
profile = self.get_profile(profile_name)
|
profile = self.get_profile(profile_name)
|
||||||
if field not in profile or not profile[field]:
|
if field not in profile or not profile[field]:
|
||||||
m = (f'Profile "{profile_name}" has no {field.upper()} configured.\n'
|
m = (f'Profile "{profile_name}" has no {field.upper()} configured.\n'
|
||||||
f'Please run `kupferbootstrap config profile init {field}`{arch_hint}')
|
f'Please run `kupferbootstrap config profile init {profile_name}`{arch_hint}')
|
||||||
raise Exception(m)
|
raise Exception(m)
|
||||||
return profile
|
return profile
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ wrapper_impls: dict[str, Wrapper] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_wrapper_type(wrapper_type: Optional[str] = None):
|
def get_wrapper_type(wrapper_type: Optional[str] = None) -> str:
|
||||||
return wrapper_type or config.file.wrapper.type
|
return wrapper_type or config.file.wrapper.type
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,14 +28,19 @@ def wrap(wrapper_type: Optional[str] = None):
|
||||||
get_wrapper_impl(wrapper_type).wrap()
|
get_wrapper_impl(wrapper_type).wrap()
|
||||||
|
|
||||||
|
|
||||||
def is_wrapped(wrapper_type: Optional[str] = None):
|
def is_wrapped(wrapper_type: Optional[str] = None) -> bool:
|
||||||
wrapper_type = get_wrapper_type(wrapper_type)
|
wrapper_type = get_wrapper_type(wrapper_type)
|
||||||
return wrapper_type != 'none' and get_wrapper_impl(wrapper_type).is_wrapped()
|
return wrapper_type != 'none' and get_wrapper_impl(wrapper_type).is_wrapped()
|
||||||
|
|
||||||
|
|
||||||
|
def needs_wrap(wrapper_type: Optional[str] = None) -> bool:
|
||||||
|
wrapper_type = wrapper_type or get_wrapper_type()
|
||||||
|
return wrapper_type != 'none' and not is_wrapped(wrapper_type) and not config.runtime.no_wrap
|
||||||
|
|
||||||
|
|
||||||
def enforce_wrap(no_wrapper=False):
|
def enforce_wrap(no_wrapper=False):
|
||||||
wrapper_type = get_wrapper_type()
|
wrapper_type = get_wrapper_type()
|
||||||
if wrapper_type != 'none' and not is_wrapped(wrapper_type) and not config.runtime.no_wrap and not no_wrapper:
|
if needs_wrap(wrapper_type) and not no_wrapper:
|
||||||
logging.info(f'Wrapping in {wrapper_type}')
|
logging.info(f'Wrapping in {wrapper_type}')
|
||||||
wrap()
|
wrap()
|
||||||
|
|
||||||
|
@ -51,6 +56,26 @@ def wrap_if_foreign_arch(arch: Arch):
|
||||||
enforce_wrap()
|
enforce_wrap()
|
||||||
|
|
||||||
|
|
||||||
|
def execute_without_exit(f, argv_override: Optional[list[str]], *args, **kwargs):
|
||||||
|
"""If no wrap is needed, executes and returns f(*args, **kwargs).
|
||||||
|
If a wrap is determined to be necessary, force a wrap with argv_override applied.
|
||||||
|
If a wrap was forced, None is returned.
|
||||||
|
WARNING: No protection against f() returning None is taken."""
|
||||||
|
if not needs_wrap():
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
assert get_wrapper_type() != 'none', "needs_wrap() should've returned False"
|
||||||
|
w = get_wrapper_impl()
|
||||||
|
w_cmd = w.argv_override
|
||||||
|
# we need to avoid throwing and catching SystemExit due to FDs getting closed otherwise
|
||||||
|
w_should_exit = w.should_exit
|
||||||
|
w.argv_override = argv_override
|
||||||
|
w.should_exit = False
|
||||||
|
w.wrap()
|
||||||
|
w.argv_override = w_cmd
|
||||||
|
w.should_exit = w_should_exit
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
nowrapper_option = click.option(
|
nowrapper_option = click.option(
|
||||||
'-w/-W',
|
'-w/-W',
|
||||||
'--force-wrapper/--no-wrapper',
|
'--force-wrapper/--no-wrapper',
|
||||||
|
|
|
@ -7,7 +7,7 @@ import sys
|
||||||
from config.state import config
|
from config.state import config
|
||||||
from exec.file import makedir
|
from exec.file import makedir
|
||||||
|
|
||||||
from .wrapper import BaseWrapper, WRAPPER_PATHS
|
from .wrapper import Wrapper, WRAPPER_PATHS
|
||||||
|
|
||||||
DOCKER_PATHS = WRAPPER_PATHS.copy()
|
DOCKER_PATHS = WRAPPER_PATHS.copy()
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ def docker_volumes_args(volume_mappings: dict[str, str]) -> list[str]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
class DockerWrapper(BaseWrapper):
|
class DockerWrapper(Wrapper):
|
||||||
type: str = 'docker'
|
type: str = 'docker'
|
||||||
|
|
||||||
def wrap(self):
|
def wrap(self):
|
||||||
|
@ -86,15 +86,21 @@ class DockerWrapper(BaseWrapper):
|
||||||
'--privileged',
|
'--privileged',
|
||||||
] + docker_volumes_args(volumes) + [tag]
|
] + docker_volumes_args(volumes) + [tag]
|
||||||
|
|
||||||
kupfer_cmd = ['kupferbootstrap', '--config', volumes[wrapped_config]] + self.filter_args_wrapper(sys.argv[1:])
|
kupfer_cmd = [
|
||||||
|
'kupferbootstrap',
|
||||||
|
'--config',
|
||||||
|
volumes[wrapped_config],
|
||||||
|
]
|
||||||
|
kupfer_cmd += self.argv_override or self.filter_args_wrapper(sys.argv[1:])
|
||||||
if config.runtime.uid:
|
if config.runtime.uid:
|
||||||
kupfer_cmd = ['wrapper_su_helper', '--uid', str(config.runtime.uid), '--username', 'kupfer', '--'] + kupfer_cmd
|
kupfer_cmd = ['wrapper_su_helper', '--uid', str(config.runtime.uid), '--username', 'kupfer', '--'] + kupfer_cmd
|
||||||
|
|
||||||
cmd = docker_cmd + kupfer_cmd
|
cmd = docker_cmd + kupfer_cmd
|
||||||
logging.debug('Wrapping in docker:' + repr(cmd))
|
logging.debug('Wrapping in docker:' + repr(cmd))
|
||||||
result = subprocess.run(cmd)
|
result = subprocess.run(cmd)
|
||||||
|
if self.should_exit:
|
||||||
exit(result.returncode)
|
exit(result.returncode)
|
||||||
|
return result.returncode
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
|
|
|
@ -15,7 +15,7 @@ WRAPPER_PATHS = CHROOT_PATHS | {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Wrapper(Protocol):
|
class WrapperProtocol(Protocol):
|
||||||
"""Wrappers wrap kupferbootstrap in some form of isolation from the host OS, i.e. docker or chroots"""
|
"""Wrappers wrap kupferbootstrap in some form of isolation from the host OS, i.e. docker or chroots"""
|
||||||
|
|
||||||
def wrap(self):
|
def wrap(self):
|
||||||
|
@ -31,15 +31,19 @@ class Wrapper(Protocol):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BaseWrapper(Wrapper):
|
class Wrapper(WrapperProtocol):
|
||||||
uuid: str
|
uuid: str
|
||||||
identifier: str
|
identifier: str
|
||||||
type: str
|
type: str
|
||||||
wrapped_config_path: str
|
wrapped_config_path: str
|
||||||
|
argv_override: Optional[list[str]]
|
||||||
|
should_exit: bool
|
||||||
|
|
||||||
def __init__(self, random_id: Optional[str] = None, name: Optional[str] = None):
|
def __init__(self, random_id: Optional[str] = None, name: Optional[str] = None):
|
||||||
self.uuid = str(random_id or uuid.uuid4())
|
self.uuid = str(random_id or uuid.uuid4())
|
||||||
self.identifier = name or f'kupferbootstrap-{self.uuid}'
|
self.identifier = name or f'kupferbootstrap-{self.uuid}'
|
||||||
|
self.argv_override = None
|
||||||
|
self.should_exit = True
|
||||||
|
|
||||||
def filter_args_wrapper(self, args):
|
def filter_args_wrapper(self, args):
|
||||||
"""filter out -c/--config since it doesn't apply in wrapper"""
|
"""filter out -c/--config since it doesn't apply in wrapper"""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue