qvm-template: Partially include docstrings and type hints.

This commit is contained in:
WillyPillow 2020-08-04 02:51:36 +08:00
parent 69cd285810
commit 41cf9f948e
No known key found for this signature in database
GPG Key ID: 3839E194B1415A9C

View File

@ -14,6 +14,7 @@ import subprocess
import sys import sys
import tempfile import tempfile
import time import time
import typing
import qubesadmin import qubesadmin
import qubesadmin.tools import qubesadmin.tools
@ -28,7 +29,8 @@ CACHE_DIR = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'qvm-template')
UNVERIFIED_SUFFIX = '.unverified' UNVERIFIED_SUFFIX = '.unverified'
LOCK_FILE = '/var/tmp/qvm-template.lck' LOCK_FILE = '/var/tmp/qvm-template.lck'
def qubes_release(): def qubes_release() -> str:
"""Return the Qubes release."""
if os.path.exists('/usr/share/qubes/marker-vm'): if os.path.exists('/usr/share/qubes/marker-vm'):
with open('/usr/share/qubes/marker-vm', 'r') as fd: with open('/usr/share/qubes/marker-vm', 'r') as fd:
# Get last line (in the format `x.x`) # Get last line (in the format `x.x`)
@ -36,7 +38,8 @@ def qubes_release():
return subprocess.check_output(['lsb_release', '-sr'], return subprocess.check_output(['lsb_release', '-sr'],
encoding='UTF-8').strip() encoding='UTF-8').strip()
def parser_gen(): def parser_gen() -> argparse.ArgumentParser:
"""Generate argument parser for the application."""
formatter = argparse.ArgumentDefaultsHelpFormatter formatter = argparse.ArgumentDefaultsHelpFormatter
parser_main = argparse.ArgumentParser(description='Qubes Template Manager', parser_main = argparse.ArgumentParser(description='Qubes Template Manager',
formatter_class=formatter) formatter_class=formatter)
@ -131,12 +134,14 @@ def parser_gen():
parser = parser_gen() parser = parser_gen()
class TemplateState(enum.Enum): class TemplateState(enum.Enum):
"""Enum representing the state of a template."""
INSTALLED = 'installed' INSTALLED = 'installed'
AVAILABLE = 'available' AVAILABLE = 'available'
EXTRA = 'extra' EXTRA = 'extra'
UPGRADABLE = 'upgradable' UPGRADABLE = 'upgradable'
def title(self): def title(self) -> str:
"""Return a long description of the state. Can be used as headings."""
#pylint: disable=invalid-name #pylint: disable=invalid-name
TEMPLATE_TITLES = { TEMPLATE_TITLES = {
TemplateState.INSTALLED: 'Installed Templates', TemplateState.INSTALLED: 'Installed Templates',
@ -147,11 +152,13 @@ class TemplateState(enum.Enum):
return TEMPLATE_TITLES[self] return TEMPLATE_TITLES[self]
class VersionSelector(enum.Enum): class VersionSelector(enum.Enum):
"""Enum representing how the candidate template version is chosen."""
LATEST = enum.auto() LATEST = enum.auto()
REINSTALL = enum.auto() REINSTALL = enum.auto()
LATEST_LOWER = enum.auto() LATEST_LOWER = enum.auto()
LATEST_HIGHER = enum.auto() LATEST_HIGHER = enum.auto()
# TODO: Docstrings and type hints for Template and DlEntry
Template = collections.namedtuple('Template', [ Template = collections.namedtuple('Template', [
'name', 'name',
'epoch', 'epoch',
@ -172,12 +179,24 @@ DlEntry = collections.namedtuple('DlEntry', [
'dlsize' 'dlsize'
]) ])
def build_version_str(evr): def build_version_str(evr: typing.Tuple[str, str, str]) -> str:
"""Return version string described by ``evr`` in (epoch, version, release)
format."""
return '%s:%s-%s' % evr return '%s:%s-%s' % evr
def is_match_spec(name, epoch, version, release, spec): def is_match_spec(name: str, epoch: str, version: str, release: str, spec: str
# Refer to "NEVRA Matching" in the DNF documentation ) -> typing.Tuple[bool, float]:
# NOTE: Currently "arch" is ignored as the templates should be of "noarch" """Check whether (name, epoch, version, release) matches the spec string.
For the algorithm, refer to section "NEVRA Matching" in the DNF
documentation.
NOTE: Currently ``arch`` is ignored as the templates should be of
``noarch``.
:return: the first element indicates whether there is a match; the second
element represents the priority of the match (lower is better).
"""
if epoch != 0: if epoch != 0:
targets = [ targets = [
f'{name}-{epoch}:{version}-{release}', f'{name}-{epoch}:{version}-{release}',
@ -197,7 +216,11 @@ def is_match_spec(name, epoch, version, release, spec):
return True, prio return True, prio
return False, float('inf') return False, float('inf')
def query_local(vm): def query_local(vm: qubesadmin.vm.QubesVM) -> Template:
"""Return Template object associated with ``vm``.
Requires the VM to be managed by qvm-template.
"""
return Template( return Template(
vm.features['template-name'], vm.features['template-name'],
vm.features['template-epoch'], vm.features['template-epoch'],
@ -211,16 +234,24 @@ def query_local(vm):
vm.features['template-summary'], vm.features['template-summary'],
vm.features['template-description'].replace('|', '\n')) vm.features['template-description'].replace('|', '\n'))
def query_local_evr(vm): def query_local_evr(vm: qubesadmin.vm.QubesVM) -> typing.Tuple[str, str, str]:
"""Return the (epoch, version, release) of ``vm``.
Requires the VM to be managed by qvm-template.
"""
return ( return (
vm.features['template-epoch'], vm.features['template-epoch'],
vm.features['template-version'], vm.features['template-version'],
vm.features['template-release']) vm.features['template-release'])
def is_managed_template(vm): def is_managed_template(vm: qubesadmin.vm.QubesVM) -> bool:
"""Return whether the VM is managed by qvm-template."""
return vm.features.get('template-name', None) == vm.name return vm.features.get('template-name', None) == vm.name
def get_managed_template_vm(app, name): def get_managed_template_vm(app: qubesadmin.app.QubesBase, name: str
) -> qubesadmin.vm.QubesVM:
"""Return the QubesVM object associated with the given name if it exists
and is managed by qvm-template, otherwise raise a parser error."""
if name not in app.domains: if name not in app.domains:
parser.error("Template '%s' not already installed." % name) parser.error("Template '%s' not already installed." % name)
vm = app.domains[name] vm = app.domains[name]
@ -228,7 +259,29 @@ def get_managed_template_vm(app, name):
parser.error("Template '%s' is not managed by qvm-template." % name) parser.error("Template '%s' is not managed by qvm-template." % name)
return vm return vm
def qrexec_popen(args, app, service, stdout=subprocess.PIPE, filter_esc=True): def qrexec_popen(
args: argparse.Namespace,
app: qubesadmin.app.QubesBase,
service: str,
stdout: int = subprocess.PIPE,
filter_esc: bool = True) -> subprocess.Popen:
"""Return Popen object that communicates with the given qrexec call.
Note that this falls back to invoking /etc/qubes-rpc/* directly if
args.updatevm is None.
:param args: arguments received by the application
:param app: Qubes application object
:param service: the qrexec call to invoke
:param stdout: Where the process stdout points to. This is passed directly
to subprocess.Popen. Defaults to subprocess.PIPE.
Note that stderr is always set to subprocess.PIPE.
:param filter_esc: whether to filter out escape sequences from
stdout/stderr. Defaults to True.
:returns: Popen object that communicates with the given qrexec call
"""
if args.updatevm: if args.updatevm:
return app.domains[args.updatevm].run_service( return app.domains[args.updatevm].run_service(
service, service,