diff --git a/qubesadmin/tests/tools/qvm_ls.py b/qubesadmin/tests/tools/qvm_ls.py index 81d37567..518a4a54 100644 --- a/qubesadmin/tests/tools/qvm_ls.py +++ b/qubesadmin/tests/tools/qvm_ls.py @@ -129,6 +129,46 @@ def test_103_list_all(self): 'dom0 Running TestVM - - -\n' 'test-vm Running TestVM - - -\n') + def test_104_wildcards(self): + app = TestApp() + app.domains = TestVMCollection( + [ + ('dom0', TestVM('dom0')), + ('debian-13', TestVM('debian-13')), + ('debian-13-minimal', TestVM('debian-13-minimal')), + ('debian-13-xfce', TestVM('debian-13-xfce')), + ('debian-sid', TestVM('debian-sid')), + ('fedora-41', TestVM('fedora-41')), + ('fedora-41-minimal', TestVM('fedora-41-minimal')), + ('fedora-41-xfce', TestVM('fedora-41-xfce')), + ('fedora-rawhide', TestVM('fedora-rawhide')), + ] + ) + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + qubesadmin.tools.qvm_ls.main(['--raw-list', 'fedora*'], app=app) + self.assertEqual(stdout.getvalue(), + 'fedora-41\nfedora-41-minimal\nfedora-41-xfce\nfedora-rawhide\n') + + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + qubesadmin.tools.qvm_ls.main(['--raw-list', '*minimal'], app=app) + self.assertEqual(stdout.getvalue(), + 'debian-13-minimal\nfedora-41-minimal\n') + + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + qubesadmin.tools.qvm_ls.main(['--raw-list', '????'], app=app) + self.assertEqual(stdout.getvalue(), + 'dom0\n') + + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + qubesadmin.tools.qvm_ls.main(['--raw-list', '??????-[rs]*'], app=app) + self.assertEqual(stdout.getvalue(), + 'debian-sid\nfedora-rawhide\n') + + with qubesadmin.tests.tools.StdoutBuffer() as stdout: + qubesadmin.tools.qvm_ls.main(['--raw-list', '??????-[!14s]*'], app=app) + self.assertEqual(stdout.getvalue(), + 'fedora-rawhide\n') + def test_110_network_tree(self): app = TestApp() app.domains = TestVMCollection( diff --git a/qubesadmin/tools/__init__.py b/qubesadmin/tools/__init__.py index 43f054d4..335f4891 100644 --- a/qubesadmin/tools/__init__.py +++ b/qubesadmin/tools/__init__.py @@ -24,6 +24,7 @@ from __future__ import print_function import argparse +import fnmatch import importlib import logging import os @@ -38,7 +39,6 @@ #: constant returned when some action should be performed on all qubes VM_ALL = object() - class QubesAction(argparse.Action): ''' Interface providing a convinience method to be called, after `namespace.app` is instantiated. @@ -175,7 +175,16 @@ def parse_qubes_app(self, parser, namespace): except KeyError: parser.error('no such domain: {!r}'.format(vm_name)) else: - for vm_name in getattr(namespace, self.dest): + destinations = set() + for destination in getattr(namespace, self.dest): + if any(wildcard in destination for wildcard in '*?[!]'): + for domain in app.domains: + if fnmatch.fnmatch(domain.name, destination): + destinations.add(domain.name) + else: + destinations.add(destination) + + for vm_name in destinations: try: namespace.domains += [app.domains[vm_name]] except KeyError: