Initial implementation for "qvm-template search".
This commit is contained in:
parent
d656554822
commit
c573faa9c0
@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import datetime
|
||||
import enum
|
||||
import fnmatch
|
||||
import functools
|
||||
import itertools
|
||||
import os
|
||||
import shutil
|
||||
@ -80,6 +82,7 @@ class TemplateState(enum.Enum):
|
||||
UPGRADABLE = 'upgradable'
|
||||
|
||||
def title(self):
|
||||
#pylint: disable=invalid-name
|
||||
TEMPLATE_TITLES = {
|
||||
TemplateState.INSTALLED: 'Installed Templates',
|
||||
TemplateState.AVAILABLE: 'Available Templates',
|
||||
@ -273,6 +276,8 @@ def qrexec_payload(args, app, spec):
|
||||
return payload
|
||||
|
||||
def qrexec_repoquery(args, app, spec='*'):
|
||||
# TODO: Perhaps expose stderr for error messages?
|
||||
# At least need to provide message that, e.g., repoid does not exist.
|
||||
proc = qrexec_popen(args, app, 'qubes.TemplateSearch')
|
||||
payload = qrexec_payload(args, app, spec)
|
||||
stdout, _ = proc.communicate(payload.encode('UTF-8'))
|
||||
@ -338,6 +343,7 @@ def list_templates(args, app, operation):
|
||||
tpl_list = []
|
||||
|
||||
def append_list(data, status):
|
||||
#pylint: disable=unused-variable
|
||||
name, epoch, version, release, reponame, dlsize, summary = data
|
||||
version_str = build_version_str((epoch, version, release))
|
||||
tpl_list.append((status, name, version_str, reponame))
|
||||
@ -386,7 +392,7 @@ def list_templates(args, app, operation):
|
||||
|
||||
if args.installed or args.all:
|
||||
for vm in app.domains:
|
||||
if 'template-install-date' in vm.features:
|
||||
if 'template-name' in vm.features:
|
||||
if not args.templates or \
|
||||
any(is_match_spec(
|
||||
vm.features['template-name'],
|
||||
@ -403,6 +409,7 @@ def list_templates(args, app, operation):
|
||||
|
||||
if args.extras:
|
||||
remote = set()
|
||||
#pylint: disable=unused-variable
|
||||
for name, epoch, version, release, reponame, dlsize, summary \
|
||||
in query_res:
|
||||
remote.add(name)
|
||||
@ -428,12 +435,88 @@ def list_templates(args, app, operation):
|
||||
if len(tpl_list) == 0:
|
||||
parser.error('No matching templates to list')
|
||||
|
||||
for k, g in itertools.groupby(tpl_list, lambda x: x[0]):
|
||||
for k, grp in itertools.groupby(tpl_list, lambda x: x[0]):
|
||||
print(k.title())
|
||||
qubesadmin.tools.print_table(list(map(lambda x: x[1:], g)))
|
||||
qubesadmin.tools.print_table(list(map(lambda x: x[1:], grp)))
|
||||
|
||||
def search(args, app):
|
||||
raise NotImplementedError
|
||||
# Search in both installed and available templates
|
||||
query_res = qrexec_repoquery(args, app)
|
||||
for vm in app.domains:
|
||||
if 'template-name' in vm.features:
|
||||
query_res.append((
|
||||
vm.features['template-name'],
|
||||
vm.features['template-epoch'],
|
||||
vm.features['template-version'],
|
||||
vm.features['template-release'],
|
||||
vm.features['template-reponame'],
|
||||
vm.get_disk_utilization(),
|
||||
vm.features['template-summary']))
|
||||
|
||||
# Get latest version for each template
|
||||
query_res_tmp = []
|
||||
for name, grp in itertools.groupby(query_res, lambda x: x[0]):
|
||||
def compare(lhs, rhs):
|
||||
return lhs if rpm.labelCompare(lhs[1:4], rhs[1:4]) < 0 else rhs
|
||||
query_res_tmp.append(functools.reduce(compare, grp))
|
||||
query_res = query_res_tmp
|
||||
|
||||
#pylint: disable=invalid-name
|
||||
WEIGHT_NAME_EXACT = 1 << 3
|
||||
WEIGHT_NAME = 1 << 2
|
||||
WEIGHT_SUMMARY = 1 << 1
|
||||
|
||||
search_res = collections.defaultdict(int)
|
||||
first = True
|
||||
for keyword in args.templates:
|
||||
local_res = collections.defaultdict(int)
|
||||
#pylint: disable=unused-variable
|
||||
for idx, (name, epoch, version, release, reponame, dlsize, summary) \
|
||||
in enumerate(query_res):
|
||||
if fnmatch.fnmatch(name, '*' + keyword + '*'):
|
||||
local_res[idx] += WEIGHT_NAME
|
||||
if fnmatch.fnmatch(summary, '*' + keyword + '*'):
|
||||
local_res[idx] += WEIGHT_SUMMARY
|
||||
if keyword == name:
|
||||
local_res[idx] += WEIGHT_NAME_EXACT
|
||||
for key, val in local_res.items():
|
||||
if args.all or first or key in search_res:
|
||||
search_res[key] += val
|
||||
first = False
|
||||
|
||||
def key_func(x):
|
||||
# Order by weight DESC, name ASC
|
||||
weight = x[1]
|
||||
name = query_res[x[0]][0]
|
||||
return (-weight, name)
|
||||
|
||||
search_res = sorted(search_res.items(), key=key_func)
|
||||
|
||||
def gen_header(idx, weight):
|
||||
# FIXME: "Exactly Matched" is printed even if the summary is not
|
||||
# exactly matching
|
||||
# TODO: Print matching keywords
|
||||
#pylint: disable=unused-variable
|
||||
name, epoch, version, release, reponame, dlsize, summary = \
|
||||
query_res[idx]
|
||||
keys = []
|
||||
if weight & WEIGHT_NAME:
|
||||
keys.append('Name')
|
||||
if weight & WEIGHT_SUMMARY:
|
||||
keys.append('Summary')
|
||||
match = 'Exactly Matched' if weight & WEIGHT_NAME_EXACT else 'Matched'
|
||||
return ' & '.join(keys) + ' ' + match
|
||||
|
||||
last_weight = -1
|
||||
for idx, weight in search_res:
|
||||
if last_weight != weight:
|
||||
last_weight = weight
|
||||
# Print headers
|
||||
# XXX: The style is different from that of DNF
|
||||
print(gen_header(idx, weight))
|
||||
name, epoch, version, release, reponame, dlsize, summary = \
|
||||
query_res[idx]
|
||||
print(name, ':', summary)
|
||||
|
||||
def get_dl_list(args, app, version_selector=VersionSelector.LATEST):
|
||||
full_candid = {}
|
||||
@ -513,6 +596,7 @@ def download(args, app, path_override=None,
|
||||
|
||||
path = path_override if path_override is not None else args.downloaddir
|
||||
for name, (ver, dlsize, reponame) in dl_list.items():
|
||||
_ = reponame # unused
|
||||
version_str = build_version_str(ver)
|
||||
spec = PACKAGE_NAME_PREFIX + name + '-' + version_str
|
||||
target = os.path.join(path, '%s.rpm' % spec)
|
||||
@ -543,7 +627,7 @@ def download(args, app, path_override=None,
|
||||
sys.exit(1)
|
||||
|
||||
def remove(args, app):
|
||||
_ = app # unused
|
||||
_ = args, app # unused
|
||||
|
||||
# Remove 'remove' entry from the args...
|
||||
operation_idx = sys.argv.index('remove')
|
||||
|
Loading…
Reference in New Issue
Block a user