Merge remote-tracking branch 'origin/pr/100'
* origin/pr/100: qvm-device: add manpage entry qvm-device: prevent parser allowing abbreviations qvm-device: handle 'list-device-classes' and 'list-classes' qubesadmin: define methods list_vmclass and list_devicesclass qubesadmin: make PEP8 happy
This commit is contained in:
commit
9158412a24
@ -28,6 +28,10 @@ Options
|
|||||||
|
|
||||||
decrease verbosity
|
decrease verbosity
|
||||||
|
|
||||||
|
.. option:: --list-device-classes
|
||||||
|
|
||||||
|
list device classes
|
||||||
|
|
||||||
Commands
|
Commands
|
||||||
========
|
========
|
||||||
|
|
||||||
@ -122,3 +126,4 @@ Authors
|
|||||||
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
|
| Joanna Rutkowska <joanna at invisiblethingslab dot com>
|
||||||
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
|
| Rafal Wojtczuk <rafal at invisiblethingslab dot com>
|
||||||
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
|
| Marek Marczykowski <marmarek at invisiblethingslab dot com>
|
||||||
|
| Frédéric Pierret <frederic.pierret at qubes dash os dot org>
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
'''
|
"""
|
||||||
Main Qubes() class and related classes.
|
Main Qubes() class and related classes.
|
||||||
'''
|
"""
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import socket
|
import socket
|
||||||
@ -40,19 +40,21 @@ import qubesadmin.config
|
|||||||
|
|
||||||
BUF_SIZE = 4096
|
BUF_SIZE = 4096
|
||||||
|
|
||||||
|
|
||||||
class VMCollection(object):
|
class VMCollection(object):
|
||||||
'''Collection of VMs objects'''
|
"""Collection of VMs objects"""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
self._vm_list = None
|
self._vm_list = None
|
||||||
self._vm_objects = {}
|
self._vm_objects = {}
|
||||||
|
|
||||||
def clear_cache(self):
|
def clear_cache(self):
|
||||||
'''Clear cached list of VMs'''
|
"""Clear cached list of VMs"""
|
||||||
self._vm_list = None
|
self._vm_list = None
|
||||||
|
|
||||||
def refresh_cache(self, force=False):
|
def refresh_cache(self, force=False):
|
||||||
'''Refresh cached list of VMs'''
|
"""Refresh cached list of VMs"""
|
||||||
if not force and self._vm_list is not None:
|
if not force and self._vm_list is not None:
|
||||||
return
|
return
|
||||||
vm_list_data = self.app.qubesd_call(
|
vm_list_data = self.app.qubesd_call(
|
||||||
@ -90,10 +92,10 @@ class VMCollection(object):
|
|||||||
return self.get_blind(item)
|
return self.get_blind(item)
|
||||||
|
|
||||||
def get_blind(self, item):
|
def get_blind(self, item):
|
||||||
'''
|
"""
|
||||||
Get a vm without downloading the list
|
Get a vm without downloading the list
|
||||||
and checking if exists
|
and checking if exists
|
||||||
'''
|
"""
|
||||||
if item not in self._vm_objects:
|
if item not in self._vm_objects:
|
||||||
cls = qubesadmin.vm.QubesVM
|
cls = qubesadmin.vm.QubesVM
|
||||||
# provide class name to constructor, if already cached (which can be
|
# provide class name to constructor, if already cached (which can be
|
||||||
@ -121,23 +123,23 @@ class VMCollection(object):
|
|||||||
yield self[vm]
|
yield self[vm]
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
'''Get list of VM names.'''
|
"""Get list of VM names."""
|
||||||
self.refresh_cache()
|
self.refresh_cache()
|
||||||
return self._vm_list.keys()
|
return self._vm_list.keys()
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
'''Get list of VM objects.'''
|
"""Get list of VM objects."""
|
||||||
self.refresh_cache()
|
self.refresh_cache()
|
||||||
return [self[name] for name in self._vm_list]
|
return [self[name] for name in self._vm_list]
|
||||||
|
|
||||||
|
|
||||||
class QubesBase(qubesadmin.base.PropertyHolder):
|
class QubesBase(qubesadmin.base.PropertyHolder):
|
||||||
'''Main Qubes application.
|
"""Main Qubes application.
|
||||||
|
|
||||||
This is a base abstract class, don't use it directly. Use specialized
|
This is a base abstract class, don't use it directly. Use specialized
|
||||||
class in py:class:`qubesadmin.Qubes` instead, which points at
|
class in py:class:`qubesadmin.Qubes` instead, which points at
|
||||||
:py:class:`QubesLocal` or :py:class:`QubesRemote`.
|
:py:class:`QubesLocal` or :py:class:`QubesRemote`.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
#: domains (VMs) collection
|
#: domains (VMs) collection
|
||||||
domains = None
|
domains = None
|
||||||
@ -163,12 +165,24 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
self._pool_drivers = None
|
self._pool_drivers = None
|
||||||
self.log = logging.getLogger('app')
|
self.log = logging.getLogger('app')
|
||||||
|
|
||||||
|
def list_vmclass(self):
|
||||||
|
"""Call Qubesd in order to obtain the vm classes list"""
|
||||||
|
vmclass = self.qubesd_call('dom0', 'admin.vmclass.List')\
|
||||||
|
.decode().splitlines()
|
||||||
|
return sorted(vmclass)
|
||||||
|
|
||||||
|
def list_deviceclass(self):
|
||||||
|
"""Call Qubesd in order to obtain the device classes list"""
|
||||||
|
deviceclasses = self.qubesd_call('dom0', 'admin.deviceclass.List')\
|
||||||
|
.decode().splitlines()
|
||||||
|
return sorted(deviceclasses)
|
||||||
|
|
||||||
def _refresh_pool_drivers(self):
|
def _refresh_pool_drivers(self):
|
||||||
'''
|
"""
|
||||||
Refresh cached storage pool drivers and their parameters.
|
Refresh cached storage pool drivers and their parameters.
|
||||||
|
|
||||||
:return: None
|
:return: None
|
||||||
'''
|
"""
|
||||||
if self._pool_drivers is None:
|
if self._pool_drivers is None:
|
||||||
pool_drivers_data = self.qubesd_call(
|
pool_drivers_data = self.qubesd_call(
|
||||||
'dom0', 'admin.pool.ListDrivers', None, None)
|
'dom0', 'admin.pool.ListDrivers', None, None)
|
||||||
@ -183,40 +197,40 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pool_drivers(self):
|
def pool_drivers(self):
|
||||||
''' Available storage pool drivers '''
|
""" Available storage pool drivers """
|
||||||
self._refresh_pool_drivers()
|
self._refresh_pool_drivers()
|
||||||
return self._pool_drivers.keys()
|
return self._pool_drivers.keys()
|
||||||
|
|
||||||
def pool_driver_parameters(self, driver):
|
def pool_driver_parameters(self, driver):
|
||||||
''' Parameters to initialize storage pool using given driver '''
|
""" Parameters to initialize storage pool using given driver """
|
||||||
self._refresh_pool_drivers()
|
self._refresh_pool_drivers()
|
||||||
return self._pool_drivers[driver]
|
return self._pool_drivers[driver]
|
||||||
|
|
||||||
def add_pool(self, name, driver, **kwargs):
|
def add_pool(self, name, driver, **kwargs):
|
||||||
''' Add a storage pool to config
|
""" Add a storage pool to config
|
||||||
|
|
||||||
:param name: name of storage pool to create
|
:param name: name of storage pool to create
|
||||||
:param driver: driver to use, see :py:meth:`pool_drivers` for
|
:param driver: driver to use, see :py:meth:`pool_drivers` for
|
||||||
available drivers
|
available drivers
|
||||||
:param kwargs: configuration parameters for storage pool,
|
:param kwargs: configuration parameters for storage pool,
|
||||||
see :py:meth:`pool_driver_parameters` for a list
|
see :py:meth:`pool_driver_parameters` for a list
|
||||||
'''
|
"""
|
||||||
# sort parameters only to ease testing, not required by API
|
# sort parameters only to ease testing, not required by API
|
||||||
payload = 'name={}\n'.format(name) + \
|
payload = 'name={}\n'.format(name) + \
|
||||||
''.join('{}={}\n'.format(key, value)
|
''.join('{}={}\n'.format(key, value)
|
||||||
for key, value in sorted(kwargs.items()))
|
for key, value in sorted(kwargs.items()))
|
||||||
self.qubesd_call('dom0', 'admin.pool.Add', driver,
|
self.qubesd_call('dom0', 'admin.pool.Add', driver,
|
||||||
payload.encode('utf-8'))
|
payload.encode('utf-8'))
|
||||||
|
|
||||||
def remove_pool(self, name):
|
def remove_pool(self, name):
|
||||||
''' Remove a storage pool '''
|
""" Remove a storage pool """
|
||||||
self.qubesd_call('dom0', 'admin.pool.Remove', name, None)
|
self.qubesd_call('dom0', 'admin.pool.Remove', name, None)
|
||||||
|
|
||||||
def get_label(self, label):
|
def get_label(self, label):
|
||||||
'''Get label as identified by index or name
|
"""Get label as identified by index or name
|
||||||
|
|
||||||
:throws KeyError: when label is not found
|
:throws KeyError: when label is not found
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# first search for name, verbatim
|
# first search for name, verbatim
|
||||||
try:
|
try:
|
||||||
@ -233,19 +247,19 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_vm_class(clsname):
|
def get_vm_class(clsname):
|
||||||
'''Find the class for a domain.
|
"""Find the class for a domain.
|
||||||
|
|
||||||
Compatibility function, client tools use str to identify domain classes.
|
Compatibility function, client tools use str to identify domain classes.
|
||||||
|
|
||||||
:param str clsname: name of the class
|
:param str clsname: name of the class
|
||||||
:return str: class
|
:return str: class
|
||||||
'''
|
"""
|
||||||
|
|
||||||
return clsname
|
return clsname
|
||||||
|
|
||||||
def add_new_vm(self, cls, name, label, template=None, pool=None,
|
def add_new_vm(self, cls, name, label, template=None, pool=None,
|
||||||
pools=None):
|
pools=None):
|
||||||
'''Create new Virtual Machine
|
"""Create new Virtual Machine
|
||||||
|
|
||||||
Example usage with custom storage pools:
|
Example usage with custom storage pools:
|
||||||
|
|
||||||
@ -264,7 +278,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
:param dict pools: storage pool for specific volumes
|
:param dict pools: storage pool for specific volumes
|
||||||
|
|
||||||
:return new VM object
|
:return new VM object
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if not isinstance(cls, str):
|
if not isinstance(cls, str):
|
||||||
cls = cls.__name__
|
cls = cls.__name__
|
||||||
@ -284,18 +298,18 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
method_prefix = 'admin.vm.CreateInPool.'
|
method_prefix = 'admin.vm.CreateInPool.'
|
||||||
if pools:
|
if pools:
|
||||||
payload += ''.join(' pool:{}={}'.format(vol, str(pool))
|
payload += ''.join(' pool:{}={}'.format(vol, str(pool))
|
||||||
for vol, pool in sorted(pools.items()))
|
for vol, pool in sorted(pools.items()))
|
||||||
method_prefix = 'admin.vm.CreateInPool.'
|
method_prefix = 'admin.vm.CreateInPool.'
|
||||||
|
|
||||||
self.qubesd_call('dom0', method_prefix + cls, template,
|
self.qubesd_call('dom0', method_prefix + cls, template,
|
||||||
payload.encode('utf-8'))
|
payload.encode('utf-8'))
|
||||||
|
|
||||||
self.domains.clear_cache()
|
self.domains.clear_cache()
|
||||||
return self.domains[name]
|
return self.domains[name]
|
||||||
|
|
||||||
def clone_vm(self, src_vm, new_name, new_cls=None,
|
def clone_vm(self, src_vm, new_name, new_cls=None, pool=None, pools=None,
|
||||||
pool=None, pools=None, ignore_errors=False, ignore_volumes=None):
|
ignore_errors=False, ignore_volumes=None):
|
||||||
'''Clone Virtual Machine
|
"""Clone Virtual Machine
|
||||||
|
|
||||||
Example usage with custom storage pools:
|
Example usage with custom storage pools:
|
||||||
|
|
||||||
@ -317,7 +331,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
like 'private' or 'root'
|
like 'private' or 'root'
|
||||||
|
|
||||||
:return new VM object
|
:return new VM object
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if pool and pools:
|
if pool and pools:
|
||||||
raise ValueError('only one of pool= and pools= can be used')
|
raise ValueError('only one of pool= and pools= can be used')
|
||||||
@ -343,7 +357,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
if ignore_volumes and volume.name in ignore_volumes:
|
if ignore_volumes and volume.name in ignore_volumes:
|
||||||
continue
|
continue
|
||||||
default_pool = getattr(self.app, 'default_pool_' + volume.name,
|
default_pool = getattr(self.app, 'default_pool_' + volume.name,
|
||||||
volume.pool)
|
volume.pool)
|
||||||
if default_pool != volume.pool:
|
if default_pool != volume.pool:
|
||||||
if pools is None:
|
if pools is None:
|
||||||
pools = {}
|
pools = {}
|
||||||
@ -356,11 +370,11 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
method_prefix = 'admin.vm.CreateInPool.'
|
method_prefix = 'admin.vm.CreateInPool.'
|
||||||
if pools:
|
if pools:
|
||||||
payload += ''.join(' pool:{}={}'.format(vol, str(pool))
|
payload += ''.join(' pool:{}={}'.format(vol, str(pool))
|
||||||
for vol, pool in sorted(pools.items()))
|
for vol, pool in sorted(pools.items()))
|
||||||
method_prefix = 'admin.vm.CreateInPool.'
|
method_prefix = 'admin.vm.CreateInPool.'
|
||||||
|
|
||||||
self.qubesd_call('dom0', method_prefix + new_cls, template,
|
self.qubesd_call('dom0', method_prefix + new_cls, template,
|
||||||
payload.encode('utf-8'))
|
payload.encode('utf-8'))
|
||||||
|
|
||||||
self.domains.clear_cache()
|
self.domains.clear_cache()
|
||||||
dst_vm = self.domains[new_name]
|
dst_vm = self.domains[new_name]
|
||||||
@ -369,7 +383,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
for prop in src_vm.property_list():
|
for prop in src_vm.property_list():
|
||||||
# handled by admin.vm.Create call
|
# handled by admin.vm.Create call
|
||||||
if prop in ('name', 'qid', 'template', 'label', 'uuid',
|
if prop in ('name', 'qid', 'template', 'label', 'uuid',
|
||||||
'installed_by_rpm'):
|
'installed_by_rpm'):
|
||||||
continue
|
continue
|
||||||
if src_vm.property_is_default(prop):
|
if src_vm.property_is_default(prop):
|
||||||
continue
|
continue
|
||||||
@ -414,7 +428,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
# FIXME: convert to qrexec calls to dom0/GUI VM
|
# FIXME: convert to qrexec calls to dom0/GUI VM
|
||||||
appmenus_cmd = \
|
appmenus_cmd = \
|
||||||
['qvm-appmenus', '--init', '--update',
|
['qvm-appmenus', '--init', '--update',
|
||||||
'--source', src_vm.name, dst_vm.name]
|
'--source', src_vm.name, dst_vm.name]
|
||||||
subprocess.check_output(appmenus_cmd, stderr=subprocess.STDOUT)
|
subprocess.check_output(appmenus_cmd, stderr=subprocess.STDOUT)
|
||||||
except OSError:
|
except OSError:
|
||||||
# this file needs to be python 2.7 compatible,
|
# this file needs to be python 2.7 compatible,
|
||||||
@ -425,7 +439,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
'Failed to clone appmenus')
|
'Failed to clone appmenus')
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
self.log.error('Failed to clone appmenus: %s',
|
self.log.error('Failed to clone appmenus: %s',
|
||||||
e.output.decode())
|
e.output.decode())
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
raise qubesadmin.exc.QubesException(
|
raise qubesadmin.exc.QubesException(
|
||||||
'Failed to clone appmenus')
|
'Failed to clone appmenus')
|
||||||
@ -453,8 +467,8 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
return dst_vm
|
return dst_vm
|
||||||
|
|
||||||
def qubesd_call(self, dest, method, arg=None, payload=None,
|
def qubesd_call(self, dest, method, arg=None, payload=None,
|
||||||
payload_stream=None):
|
payload_stream=None):
|
||||||
'''
|
"""
|
||||||
Execute Admin API method.
|
Execute Admin API method.
|
||||||
|
|
||||||
Only one of `payload` and `payload_stream` can be specified.
|
Only one of `payload` and `payload_stream` can be specified.
|
||||||
@ -467,14 +481,14 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
:return: Data returned by qubesd (string)
|
:return: Data returned by qubesd (string)
|
||||||
|
|
||||||
.. warning:: *payload_stream* will get closed by this function
|
.. warning:: *payload_stream* will get closed by this function
|
||||||
'''
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'qubesd_call not implemented in QubesBase class; use specialized '
|
'qubesd_call not implemented in QubesBase class; use specialized '
|
||||||
'class: qubesadmin.Qubes()')
|
'class: qubesadmin.Qubes()')
|
||||||
|
|
||||||
def run_service(self, dest, service, filter_esc=False, user=None,
|
def run_service(self, dest, service, filter_esc=False, user=None,
|
||||||
localcmd=None, wait=True, **kwargs):
|
localcmd=None, wait=True, **kwargs):
|
||||||
'''Run qrexec service in a given destination
|
"""Run qrexec service in a given destination
|
||||||
|
|
||||||
*kwargs* are passed verbatim to :py:meth:`subprocess.Popen`.
|
*kwargs* are passed verbatim to :py:meth:`subprocess.Popen`.
|
||||||
|
|
||||||
@ -485,24 +499,25 @@ class QubesBase(qubesadmin.base.PropertyHolder):
|
|||||||
emulator
|
emulator
|
||||||
:param str user: username to run service as
|
:param str user: username to run service as
|
||||||
:param str localcmd: Command to connect stdin/stdout to
|
:param str localcmd: Command to connect stdin/stdout to
|
||||||
|
:param bool wait: Wait service run
|
||||||
:rtype: subprocess.Popen
|
:rtype: subprocess.Popen
|
||||||
'''
|
"""
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'run_service not implemented in QubesBase class; use specialized '
|
'run_service not implemented in QubesBase class; use specialized '
|
||||||
'class: qubesadmin.Qubes()')
|
'class: qubesadmin.Qubes()')
|
||||||
|
|
||||||
|
|
||||||
class QubesLocal(QubesBase):
|
class QubesLocal(QubesBase):
|
||||||
'''Application object communicating through local socket.
|
"""Application object communicating through local socket.
|
||||||
|
|
||||||
Used when running in dom0.
|
Used when running in dom0.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
qubesd_connection_type = 'socket'
|
qubesd_connection_type = 'socket'
|
||||||
|
|
||||||
def qubesd_call(self, dest, method, arg=None, payload=None,
|
def qubesd_call(self, dest, method, arg=None, payload=None,
|
||||||
payload_stream=None):
|
payload_stream=None):
|
||||||
'''
|
"""
|
||||||
Execute Admin API method.
|
Execute Admin API method.
|
||||||
|
|
||||||
Only one of `payload` and `payload_stream` can be specified.
|
Only one of `payload` and `payload_stream` can be specified.
|
||||||
@ -515,7 +530,7 @@ class QubesLocal(QubesBase):
|
|||||||
:return: Data returned by qubesd (string)
|
:return: Data returned by qubesd (string)
|
||||||
|
|
||||||
.. warning:: *payload_stream* will get closed by this function
|
.. warning:: *payload_stream* will get closed by this function
|
||||||
'''
|
"""
|
||||||
if payload and payload_stream:
|
if payload and payload_stream:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Only one of payload and payload_stream can be used')
|
'Only one of payload and payload_stream can be used')
|
||||||
@ -530,11 +545,11 @@ class QubesLocal(QubesBase):
|
|||||||
raise qubesadmin.exc.QubesDaemonCommunicationError(
|
raise qubesadmin.exc.QubesDaemonCommunicationError(
|
||||||
'{} not found'.format(method_path))
|
'{} not found'.format(method_path))
|
||||||
command = ['env', 'QREXEC_REMOTE_DOMAIN=dom0',
|
command = ['env', 'QREXEC_REMOTE_DOMAIN=dom0',
|
||||||
'QREXEC_REQUESTED_TARGET=' + dest, method_path, arg]
|
'QREXEC_REQUESTED_TARGET=' + dest, method_path, arg]
|
||||||
if os.getuid() != 0:
|
if os.getuid() != 0:
|
||||||
command.insert(0, 'sudo')
|
command.insert(0, 'sudo')
|
||||||
proc = subprocess.Popen(command, stdin=payload_stream,
|
proc = subprocess.Popen(command, stdin=payload_stream,
|
||||||
stdout=subprocess.PIPE)
|
stdout=subprocess.PIPE)
|
||||||
payload_stream.close()
|
payload_stream.close()
|
||||||
(return_data, _) = proc.communicate()
|
(return_data, _) = proc.communicate()
|
||||||
return self._parse_qubesd_response(return_data)
|
return self._parse_qubesd_response(return_data)
|
||||||
@ -561,8 +576,8 @@ class QubesLocal(QubesBase):
|
|||||||
return self._parse_qubesd_response(return_data)
|
return self._parse_qubesd_response(return_data)
|
||||||
|
|
||||||
def run_service(self, dest, service, filter_esc=False, user=None,
|
def run_service(self, dest, service, filter_esc=False, user=None,
|
||||||
localcmd=None, wait=True, **kwargs):
|
localcmd=None, wait=True, **kwargs):
|
||||||
'''Run qrexec service in a given destination
|
"""Run qrexec service in a given destination
|
||||||
|
|
||||||
:param str dest: Destination - may be a VM name or empty
|
:param str dest: Destination - may be a VM name or empty
|
||||||
string for default (for a given service)
|
string for default (for a given service)
|
||||||
@ -572,9 +587,8 @@ class QubesLocal(QubesBase):
|
|||||||
:param str user: username to run service as
|
:param str user: username to run service as
|
||||||
:param str localcmd: Command to connect stdin/stdout to
|
:param str localcmd: Command to connect stdin/stdout to
|
||||||
:param bool wait: wait for remote process to finish
|
:param bool wait: wait for remote process to finish
|
||||||
:param int connect_timeout: qrexec client connection timeout
|
|
||||||
:rtype: subprocess.Popen
|
:rtype: subprocess.Popen
|
||||||
'''
|
"""
|
||||||
|
|
||||||
if not dest:
|
if not dest:
|
||||||
raise ValueError('Empty destination name allowed only from a VM')
|
raise ValueError('Empty destination name allowed only from a VM')
|
||||||
@ -600,23 +614,23 @@ class QubesLocal(QubesBase):
|
|||||||
kwargs.setdefault('stdin', subprocess.PIPE)
|
kwargs.setdefault('stdin', subprocess.PIPE)
|
||||||
kwargs.setdefault('stdout', subprocess.PIPE)
|
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||||
kwargs.setdefault('stderr', subprocess.PIPE)
|
kwargs.setdefault('stderr', subprocess.PIPE)
|
||||||
proc = subprocess.Popen([qubesadmin.config.QREXEC_CLIENT] +
|
proc = subprocess.Popen(
|
||||||
qrexec_opts + ['{}:QUBESRPC {} dom0'.format(user, service)],
|
[qubesadmin.config.QREXEC_CLIENT] + qrexec_opts + [
|
||||||
**kwargs)
|
'{}:QUBESRPC {} dom0'.format(user, service)], **kwargs)
|
||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
class QubesRemote(QubesBase):
|
class QubesRemote(QubesBase):
|
||||||
'''Application object communicating through qrexec services.
|
"""Application object communicating through qrexec services.
|
||||||
|
|
||||||
Used when running in VM.
|
Used when running in VM.
|
||||||
'''
|
"""
|
||||||
|
|
||||||
qubesd_connection_type = 'qrexec'
|
qubesd_connection_type = 'qrexec'
|
||||||
|
|
||||||
def qubesd_call(self, dest, method, arg=None, payload=None,
|
def qubesd_call(self, dest, method, arg=None, payload=None,
|
||||||
payload_stream=None):
|
payload_stream=None):
|
||||||
'''
|
"""
|
||||||
Execute Admin API method.
|
Execute Admin API method.
|
||||||
|
|
||||||
Only one of `payload` and `payload_stream` can be specified.
|
Only one of `payload` and `payload_stream` can be specified.
|
||||||
@ -629,7 +643,7 @@ class QubesRemote(QubesBase):
|
|||||||
:return: Data returned by qubesd (string)
|
:return: Data returned by qubesd (string)
|
||||||
|
|
||||||
.. warning:: *payload_stream* will get closed by this function
|
.. warning:: *payload_stream* will get closed by this function
|
||||||
'''
|
"""
|
||||||
if payload and payload_stream:
|
if payload and payload_stream:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Only one of payload and payload_stream can be used')
|
'Only one of payload and payload_stream can be used')
|
||||||
@ -637,10 +651,10 @@ class QubesRemote(QubesBase):
|
|||||||
if arg is not None:
|
if arg is not None:
|
||||||
service_name += '+' + arg
|
service_name += '+' + arg
|
||||||
p = subprocess.Popen([qubesadmin.config.QREXEC_CLIENT_VM,
|
p = subprocess.Popen([qubesadmin.config.QREXEC_CLIENT_VM,
|
||||||
dest, service_name],
|
dest, service_name],
|
||||||
stdin=(payload_stream or subprocess.PIPE),
|
stdin=(payload_stream or subprocess.PIPE),
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
if payload_stream is not None:
|
if payload_stream is not None:
|
||||||
payload_stream.close()
|
payload_stream.close()
|
||||||
(stdout, stderr) = p.communicate(payload)
|
(stdout, stderr) = p.communicate(payload)
|
||||||
@ -651,8 +665,8 @@ class QubesRemote(QubesBase):
|
|||||||
return self._parse_qubesd_response(stdout)
|
return self._parse_qubesd_response(stdout)
|
||||||
|
|
||||||
def run_service(self, dest, service, filter_esc=False, user=None,
|
def run_service(self, dest, service, filter_esc=False, user=None,
|
||||||
localcmd=None, wait=True, **kwargs):
|
localcmd=None, wait=True, **kwargs):
|
||||||
'''Run qrexec service in a given destination
|
"""Run qrexec service in a given destination
|
||||||
|
|
||||||
:param str dest: Destination - may be a VM name or empty
|
:param str dest: Destination - may be a VM name or empty
|
||||||
string for default (for a given service)
|
string for default (for a given service)
|
||||||
@ -663,7 +677,7 @@ class QubesRemote(QubesBase):
|
|||||||
:param str localcmd: Command to connect stdin/stdout to
|
:param str localcmd: Command to connect stdin/stdout to
|
||||||
:param bool wait: wait for process to finish
|
:param bool wait: wait for process to finish
|
||||||
:rtype: subprocess.Popen
|
:rtype: subprocess.Popen
|
||||||
'''
|
"""
|
||||||
if filter_esc:
|
if filter_esc:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
'filter_esc not implemented for calls from VM')
|
'filter_esc not implemented for calls from VM')
|
||||||
@ -685,7 +699,7 @@ class QubesRemote(QubesBase):
|
|||||||
kwargs.setdefault('stdin', subprocess.PIPE)
|
kwargs.setdefault('stdin', subprocess.PIPE)
|
||||||
kwargs.setdefault('stdout', subprocess.PIPE)
|
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||||
kwargs.setdefault('stderr', subprocess.PIPE)
|
kwargs.setdefault('stderr', subprocess.PIPE)
|
||||||
proc = subprocess.Popen([qubesadmin.config.QREXEC_CLIENT_VM,
|
proc = subprocess.Popen(
|
||||||
dest or '', service] + (shlex.split(localcmd) if localcmd else []),
|
[qubesadmin.config.QREXEC_CLIENT_VM, dest or '', service] + (
|
||||||
**kwargs)
|
shlex.split(localcmd) if localcmd else []), **kwargs)
|
||||||
return proc
|
return proc
|
||||||
|
@ -303,17 +303,7 @@ class DeviceManager(dict):
|
|||||||
return self[key]
|
return self[key]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(self._get_device_classes())
|
return iter(self._vm.app.list_deviceclass())
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
return self._get_device_classes()
|
return self._vm.app.list_deviceclass()
|
||||||
|
|
||||||
def _get_device_classes(self):
|
|
||||||
"""Function used to call Qubesd in order to obtain
|
|
||||||
the device classes list
|
|
||||||
"""
|
|
||||||
device_classes = \
|
|
||||||
self._vm.app.qubesd_call('dom0', 'admin.deviceclass.List').decode()
|
|
||||||
device_classes = sorted(device_classes.splitlines())
|
|
||||||
|
|
||||||
return device_classes
|
|
||||||
|
1
qubesadmin/qubesadmin
Symbolic link
1
qubesadmin/qubesadmin
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
/home/user/qubes-builder/qubes-src/core-admin-client/qubesadmin
|
@ -103,9 +103,8 @@ def main(args=None, app=None):
|
|||||||
args = parser.parse_args(args, app=app)
|
args = parser.parse_args(args, app=app)
|
||||||
|
|
||||||
if args.help_classes:
|
if args.help_classes:
|
||||||
vm_classes = args.app.qubesd_call('dom0', 'admin.vmclass.List').decode()
|
vm_classes = args.app.list_vmclass()
|
||||||
vm_classes = vm_classes.splitlines()
|
print('\n'.join(vm_classes))
|
||||||
print('\n'.join(sorted(vm_classes)))
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
pools = {}
|
pools = {}
|
||||||
|
@ -211,6 +211,7 @@ def get_parser(device_class=None):
|
|||||||
want_app=True)
|
want_app=True)
|
||||||
parser.register('action', 'parsers',
|
parser.register('action', 'parsers',
|
||||||
qubesadmin.tools.AliasedSubParsersAction)
|
qubesadmin.tools.AliasedSubParsersAction)
|
||||||
|
parser.allow_abbrev = False
|
||||||
if device_class:
|
if device_class:
|
||||||
parser.add_argument('devclass', const=device_class,
|
parser.add_argument('devclass', const=device_class,
|
||||||
action='store_const',
|
action='store_const',
|
||||||
@ -263,6 +264,9 @@ def get_parser(device_class=None):
|
|||||||
attach_parser.set_defaults(func=attach_device)
|
attach_parser.set_defaults(func=attach_device)
|
||||||
detach_parser.set_defaults(func=detach_device)
|
detach_parser.set_defaults(func=detach_device)
|
||||||
|
|
||||||
|
parser.add_argument('--list-device-classes', action='store_true',
|
||||||
|
default=False)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -272,7 +276,13 @@ def main(args=None, app=None):
|
|||||||
devclass = None
|
devclass = None
|
||||||
if basename.startswith('qvm-') and basename != 'qvm-device':
|
if basename.startswith('qvm-') and basename != 'qvm-device':
|
||||||
devclass = basename[4:]
|
devclass = basename[4:]
|
||||||
|
|
||||||
args = get_parser(devclass).parse_args(args, app=app)
|
args = get_parser(devclass).parse_args(args, app=app)
|
||||||
|
|
||||||
|
if args.list_device_classes:
|
||||||
|
print('\n'.join(qubesadmin.Qubes().list_deviceclass()))
|
||||||
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
args.func(args)
|
args.func(args)
|
||||||
except qubesadmin.exc.QubesException as e:
|
except qubesadmin.exc.QubesException as e:
|
||||||
@ -282,4 +292,10 @@ def main(args=None, app=None):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# Special treatment for '--list-device-classes' (alias --list-classes)
|
||||||
|
curr_action = sys.argv[1:]
|
||||||
|
if set(curr_action).intersection(
|
||||||
|
{'--list-device-classes', '--list-classes'}):
|
||||||
|
sys.exit(main(args=['', '--list-device-classes']))
|
||||||
|
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
Loading…
Reference in New Issue
Block a user