Honor custom CLI defaults in guided VM creation
This commit is contained in:
+1
-1
@@ -100,7 +100,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
if args.command in (None, 'menu'):
|
if args.command in (None, 'menu'):
|
||||||
from .launch import session_manifest
|
from .launch import session_manifest
|
||||||
from .menu import run_menu
|
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':
|
if args.command == 'download':
|
||||||
from .media import download_iso
|
from .media import download_iso
|
||||||
print(download_iso(args.url, sha256=args.sha256, progress=_download_progress()))
|
print(download_iso(args.url, sha256=args.sha256, progress=_download_progress()))
|
||||||
|
|||||||
+10
-5
@@ -96,7 +96,7 @@ def _source_prompt(label: str, supplied: str | None = None) -> str:
|
|||||||
return value
|
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)
|
source = _source_prompt('ISO path or direct HTTP(S) URL (blank cancels)> ', source)
|
||||||
name = validate_name(_source_prompt('New VM name (blank cancels)> '))
|
name = validate_name(_source_prompt('New VM name (blank cancels)> '))
|
||||||
# Reject duplicates before asking for options or downloading an image.
|
# 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')
|
cpus = int(input('CPUs [2]> ').strip() or '2')
|
||||||
disk_size = input('Disk size [40G]> ').strip() or '40G'
|
disk_size = input('Disk size [40G]> ').strip() or '40G'
|
||||||
sha256 = input('SHA-256 (optional)> ').strip() or None
|
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 []))
|
'--cpus', str(cpus), '--disk-size', disk_size] + (['--sha256', sha256] if sha256 else []))
|
||||||
code = run_source(runtime, source, name=name, memory=memory, cpus=cpus,
|
code = run_source(runtime, source, name=name, memory=memory, cpus=cpus,
|
||||||
disk_size=disk_size, sha256=sha256, manifest=manifest,
|
disk_size=disk_size, sha256=sha256, manifest=manifest,
|
||||||
defaults=Path('/etc/hydra/defaults.toml'), progress=_progress())
|
defaults=defaults, progress=_progress())
|
||||||
return code, name
|
return code, name
|
||||||
|
|
||||||
|
|
||||||
@@ -142,7 +145,8 @@ def _redact_url(source: str) -> str:
|
|||||||
return source
|
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)
|
last = _last_selected(runtime)
|
||||||
while True:
|
while True:
|
||||||
profiles = _profiles(runtime)
|
profiles = _profiles(runtime)
|
||||||
@@ -205,7 +209,8 @@ def run_menu(runtime: Runtime, *, manifest: Path | None = None) -> int:
|
|||||||
elif action in (':run', ':new'):
|
elif action in (':run', ':new'):
|
||||||
if len(arguments) > 1:
|
if len(arguments) > 1:
|
||||||
raise ValueError('Use :run followed by one quoted ISO path or URL, or follow the prompts.')
|
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:
|
if code == 0:
|
||||||
_remember(runtime, name)
|
_remember(runtime, name)
|
||||||
last = name
|
last = name
|
||||||
|
|||||||
@@ -112,3 +112,48 @@ class CLITests(unittest.TestCase):
|
|||||||
self.assertNotIn('supersecret', result.stderr)
|
self.assertNotIn('supersecret', result.stderr)
|
||||||
self.assertNotIn('private', result.stderr)
|
self.assertNotIn('private', result.stderr)
|
||||||
self.assertNotIn('Traceback', 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))
|
||||||
|
|||||||
Reference in New Issue
Block a user