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 flavours.flavour import get_flavours
|
||||
from wrapper import execute_without_exit
|
||||
|
||||
from .scheme import Profile
|
||||
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]:
|
||||
devices = get_devices()
|
||||
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()):
|
||||
print(devices[dev])
|
||||
return prompt_choice(current, f'profiles.{profile_name}.device', devices.keys())
|
||||
|
||||
|
||||
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))
|
||||
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()):
|
||||
print(flavours[f])
|
||||
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.
|
||||
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 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
|
||||
if retry_ctx:
|
||||
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)
|
||||
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')
|
||||
@noninteractive_flag
|
||||
|
@ -224,6 +238,7 @@ def cmd_config_init(
|
|||
):
|
||||
"""Initialize the config file"""
|
||||
if not non_interactive:
|
||||
logging.info(CONFIG_MSG)
|
||||
results: dict[str, dict] = {}
|
||||
for section in sections:
|
||||
if section not in CONFIG_SECTIONS:
|
||||
|
@ -239,7 +254,14 @@ def cmd_config_init(
|
|||
results[section][key] = result
|
||||
|
||||
config.update(results)
|
||||
print("Main configuration complete")
|
||||
if not noop:
|
||||
if prompt_for_save(ctx):
|
||||
config.write()
|
||||
else:
|
||||
return
|
||||
if 'profiles' in sections:
|
||||
print("Configuring profiles")
|
||||
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)
|
||||
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.
|
||||
"""
|
||||
config.enforce_config_loaded()
|
||||
logging.info(CONFIG_MSG)
|
||||
config_copy = deepcopy(config.file)
|
||||
for pair in key_vals:
|
||||
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)
|
||||
if name == 'current':
|
||||
raise Exception("profile name 'current' not allowed")
|
||||
logging.info(CONFIG_MSG)
|
||||
name = name or config.file.profiles.current
|
||||
if name in config.file.profiles:
|
||||
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)
|
||||
if not noop:
|
||||
if not prompt_for_save(ctx):
|
||||
logging.info("Not saving.")
|
||||
return
|
||||
|
||||
config.write()
|
||||
else:
|
||||
logging.info(f'--noop passed, not writing to {config.runtime.config_file}!')
|
||||
|
|
|
@ -255,7 +255,7 @@ class ConfigStateHolder:
|
|||
profile = self.get_profile(profile_name)
|
||||
if field not in profile or not profile[field]:
|
||||
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)
|
||||
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
|
||||
|
||||
|
||||
|
@ -28,14 +28,19 @@ def wrap(wrapper_type: Optional[str] = None):
|
|||
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)
|
||||
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):
|
||||
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}')
|
||||
wrap()
|
||||
|
||||
|
@ -51,6 +56,26 @@ def wrap_if_foreign_arch(arch: Arch):
|
|||
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(
|
||||
'-w/-W',
|
||||
'--force-wrapper/--no-wrapper',
|
||||
|
|
|
@ -7,7 +7,7 @@ import sys
|
|||
from config.state import config
|
||||
from exec.file import makedir
|
||||
|
||||
from .wrapper import BaseWrapper, WRAPPER_PATHS
|
||||
from .wrapper import Wrapper, WRAPPER_PATHS
|
||||
|
||||
DOCKER_PATHS = WRAPPER_PATHS.copy()
|
||||
|
||||
|
@ -19,7 +19,7 @@ def docker_volumes_args(volume_mappings: dict[str, str]) -> list[str]:
|
|||
return result
|
||||
|
||||
|
||||
class DockerWrapper(BaseWrapper):
|
||||
class DockerWrapper(Wrapper):
|
||||
type: str = 'docker'
|
||||
|
||||
def wrap(self):
|
||||
|
@ -86,15 +86,21 @@ class DockerWrapper(BaseWrapper):
|
|||
'--privileged',
|
||||
] + 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:
|
||||
kupfer_cmd = ['wrapper_su_helper', '--uid', str(config.runtime.uid), '--username', 'kupfer', '--'] + kupfer_cmd
|
||||
|
||||
cmd = docker_cmd + kupfer_cmd
|
||||
logging.debug('Wrapping in docker:' + repr(cmd))
|
||||
result = subprocess.run(cmd)
|
||||
|
||||
exit(result.returncode)
|
||||
if self.should_exit:
|
||||
exit(result.returncode)
|
||||
return result.returncode
|
||||
|
||||
def stop(self):
|
||||
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"""
|
||||
|
||||
def wrap(self):
|
||||
|
@ -31,15 +31,19 @@ class Wrapper(Protocol):
|
|||
"""
|
||||
|
||||
|
||||
class BaseWrapper(Wrapper):
|
||||
class Wrapper(WrapperProtocol):
|
||||
uuid: str
|
||||
identifier: str
|
||||
type: 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):
|
||||
self.uuid = str(random_id or uuid.uuid4())
|
||||
self.identifier = name or f'kupferbootstrap-{self.uuid}'
|
||||
self.argv_override = None
|
||||
self.should_exit = True
|
||||
|
||||
def filter_args_wrapper(self, args):
|
||||
"""filter out -c/--config since it doesn't apply in wrapper"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue