From 1bc7965ccaddb76abc78aafa82bb59358cf4dc0e Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 13 Oct 2026 15:40:21 +0300 Subject: [PATCH] Honor custom CLI defaults in guided VM creation --- hydra/cli.py | 2 +- hydra/menu.py | 15 ++++++++++----- tests/test_cli.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/hydra/cli.py b/hydra/cli.py index 1e593b8..7b72f23 100644 --- a/hydra/cli.py +++ b/hydra/cli.py @@ -100,7 +100,7 @@ def main(argv: list[str] | None = None) -> int: if args.command in (None, 'menu'): from .launch import session_manifest from .menu import run_menu - return run_menu(runtime, manifest=session_manifest(runtime)) + return run_menu(runtime, manifest=session_manifest(runtime), defaults=args.defaults) if args.command == 'download': from .media import download_iso print(download_iso(args.url, sha256=args.sha256, progress=_download_progress())) diff --git a/hydra/menu.py b/hydra/menu.py index e8fb312..f1f3ef2 100644 --- a/hydra/menu.py +++ b/hydra/menu.py @@ -96,7 +96,7 @@ def _source_prompt(label: str, supplied: str | None = None) -> str: return value -def _guided_run(runtime: Runtime, manifest: Path | None, source: str | None): +def _guided_run(runtime: Runtime, manifest: Path | None, source: str | None, *, defaults: Path): source = _source_prompt('ISO path or direct HTTP(S) URL (blank cancels)> ', source) name = validate_name(_source_prompt('New VM name (blank cancels)> ')) # Reject duplicates before asking for options or downloading an image. @@ -106,11 +106,14 @@ def _guided_run(runtime: Runtime, manifest: Path | None, source: str | None): cpus = int(input('CPUs [2]> ').strip() or '2') disk_size = input('Disk size [40G]> ').strip() or '40G' sha256 = input('SHA-256 (optional)> ').strip() or None - _preview(['hydra', 'run', _redact_url(source), '--name', name, '--memory', str(memory), + prefix = ['hydra'] + if defaults != Path('/etc/hydra/defaults.toml'): + prefix += ['--defaults', str(defaults)] + _preview(prefix + ['run', _redact_url(source), '--name', name, '--memory', str(memory), '--cpus', str(cpus), '--disk-size', disk_size] + (['--sha256', sha256] if sha256 else [])) code = run_source(runtime, source, name=name, memory=memory, cpus=cpus, disk_size=disk_size, sha256=sha256, manifest=manifest, - defaults=Path('/etc/hydra/defaults.toml'), progress=_progress()) + defaults=defaults, progress=_progress()) return code, name @@ -142,7 +145,8 @@ def _redact_url(source: str) -> str: return source -def run_menu(runtime: Runtime, *, manifest: Path | None = None) -> int: +def run_menu(runtime: Runtime, *, manifest: Path | None = None, + defaults: Path = Path('/etc/hydra/defaults.toml')) -> int: last = _last_selected(runtime) while True: profiles = _profiles(runtime) @@ -205,7 +209,8 @@ def run_menu(runtime: Runtime, *, manifest: Path | None = None) -> int: elif action in (':run', ':new'): if len(arguments) > 1: raise ValueError('Use :run followed by one quoted ISO path or URL, or follow the prompts.') - code, name = _guided_run(runtime, manifest, arguments[0] if arguments else None) + code, name = _guided_run(runtime, manifest, arguments[0] if arguments else None, + defaults=defaults) if code == 0: _remember(runtime, name) last = name diff --git a/tests/test_cli.py b/tests/test_cli.py index 268adc6..b5d6837 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -112,3 +112,48 @@ class CLITests(unittest.TestCase): self.assertNotIn('supersecret', result.stderr) self.assertNotIn('private', result.stderr) self.assertNotIn('Traceback', result.stderr) + + def guided_defaults_profile(self, command): + import contextlib + import io + import os + from unittest.mock import patch + from hydra.cli import main + from hydra.runtime import Runtime + from test_media import iso_bytes + from test_runtime import FAKE_IMG, FAKE_QEMU + + self.defaults.write_text('network="bridge"\nbridge="testbr0"\nwidth=1600\nheight=900\nrefresh_millihz=75000\n') + iso = self.root / 'guided.iso' + iso.write_bytes(iso_bytes()) + qemu = self.root / 'fake-qemu' + qemu.write_text(FAKE_QEMU); qemu.chmod(0o700) + image = self.root / 'fake-img' + image.write_text(FAKE_IMG); image.chmod(0o700) + viewer = self.root / 'fake-viewer' + viewer.write_text('#!/bin/sh\necho --hide-toolbar\n'); viewer.chmod(0o700) + runtime = Runtime(self.root / 'config', self.root / 'state', self.root / 'run') + def cleanup(): + if runtime.status('guided')['status'] == 'running': + runtime.stop('guided', force=True) + self.addCleanup(cleanup) + inputs = [':run ' + str(iso), 'guided', '512', '1', '8M', '', ':quit'] + with patch.dict(os.environ, {'HYDRA_QEMU': str(qemu), 'HYDRA_QEMU_IMG': str(image), + 'HYDRA_VIEWER': str(viewer), 'WAYLAND_DISPLAY': 'unused', + 'HYDRA_SESSION_MANIFEST': ''}), \ + patch('sys.stdin.isatty', return_value=True), patch('builtins.input', side_effect=inputs), \ + patch('hydra.launch.available_memory_mib', return_value=8192), \ + patch('hydra.session.view', return_value=0), contextlib.redirect_stdout(io.StringIO()): + self.assertEqual(main(self.base[2:] + command), 0) + self.assertEqual(runtime.status('guided')['status'], 'running') + return runtime.profile('guided') + + def test_menu_guided_run_honors_cli_custom_network_and_display_defaults(self): + profile = self.guided_defaults_profile(['menu']) + self.assertEqual((profile.network, profile.bridge, profile.width, profile.height, profile.refresh_millihz), + ('bridge', 'testbr0', 1600, 900, 75000)) + + def test_plain_interactive_guided_run_honors_cli_custom_network_and_display_defaults(self): + profile = self.guided_defaults_profile([]) + self.assertEqual((profile.network, profile.bridge, profile.width, profile.height, profile.refresh_millihz), + ('bridge', 'testbr0', 1600, 900, 75000))