diff --git a/qubesadmin/tests/tools/qvm_device.py b/qubesadmin/tests/tools/qvm_device.py index e82f382f..cfb01203 100644 --- a/qubesadmin/tests/tools/qvm_device.py +++ b/qubesadmin/tests/tools/qvm_device.py @@ -379,6 +379,36 @@ def test_032_assign_ask_and_options(self): app=self.app) self.assertIn('Assigned.', buf.getvalue()) self.assertIn('now restart domain', buf.getvalue()) + self.assertIn('-o read-only=yes', buf.getvalue()) + self.assertAllCalled() + + def test_032b_assign_option_in_hint(self): + """ Test that -o options are copied to the suggested attach command """ + self.app.domains['test-vm2'].is_running = lambda: True + self.app.expected_calls[( + 'test-vm2', 'admin.vm.device.testclass.Assign', + 'test-vm1+dev1+dead+beef+babe+u012345', + b"device_id='dead:beef:babe:u012345' port_id='dev1' " + b"devclass='testclass' backend_domain='test-vm1' " + b"mode='auto-attach' frontend_domain='test-vm2' " + b"_frontend-dev='xvdl'" + )] = b'0\0' + self.app.expected_calls[( + 'test-vm2', 'admin.vm.device.testclass.Attached', None, None + )] = b'0\0' + self.app.expected_calls[( + 'test-vm2', 'admin.vm.property.GetAll', None, None + )] = b'2\0QubesDaemonNoResponseError\0\0err\0' + self.app.expected_calls[( + 'test-vm2', 'admin.vm.property.Get', 'devices_denied', None + )] = b'0\0default=False type=str ' + with qubesadmin.tests.tools.StdoutBuffer() as buf: + qubesadmin.tools.qvm_device.main( + ['testclass', 'assign', '-o', 'frontend-dev=xvdl', + 'test-vm2', 'test-vm1:dev1'], + app=self.app) + self.assertIn('Assigned.', buf.getvalue()) + self.assertIn('-o frontend-dev=xvdl', buf.getvalue()) self.assertAllCalled() def test_033_assign_invalid(self): diff --git a/qubesadmin/tools/qvm_device.py b/qubesadmin/tools/qvm_device.py index 931d50c8..decbc4f4 100644 --- a/qubesadmin/tools/qvm_device.py +++ b/qubesadmin/tools/qvm_device.py @@ -356,11 +356,18 @@ def assign_device(args): _print_attach_hint(assignment, vm) +def _build_options_str(options): + """Build CLI option flags string from assignment options dict.""" + parts = [f"-o {key}={value}" for key, value in options.items()] + return (" " + " ".join(parts)) if parts else "" + + def _print_attach_hint(assignment, vm): # pylint: disable=missing-function-docstring attached = vm.devices[assignment.devclass].get_attached_devices() + options_str = _build_options_str(assignment.options) ports = [ - f"\tqvm-{assignment.devclass} attach {vm} " + f"\tqvm-{assignment.devclass} attach{options_str} {vm} " f"{assignment.backend_domain}:{dev.port_id}" for dev in assignment.devices if dev not in attached and not isinstance(dev, UnknownDevice)