1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/vrtManager/virtsecrets.py
catborise 491819ed64 feat(vrtManager): the libvirt layer's messages show in the reader's language
vrtManager's errors (some 140: a disk that does not move, a network
address outside its subnet, a host device given to another VM, the
disks a migration cannot copy) were English f-strings, so no catalog
could translate them, on pages or in jobs.

They are marked texts with their values now; a variant that differs by
a word (internal or external, running or persistent, with a subnet or
without) is a whole message of its own, and a list of problems is a
list of messages. OperationError reads in the active language, so the
pages that show one translate it as they are; its args stay English, and
so do the logs and the audit log. Host device problems and the Migrate
tab's reasons a disk is not copied are messages too.
2026-11-03 00:05:34 +03:00

119 lines
5.5 KiB
Python

from django.utils.translation import gettext_noop
import base64
from libvirt import VIR_ERR_NO_STORAGE_POOL, VIR_STORAGE_XML_INACTIVE, libvirtError
from lxml import etree
from vrtManager import util
from vrtManager.connection import wvmConnect
# the element of <usage> that holds the usage, per usage type
USAGE_ELEMENTS = {"ceph": "name", "volume": "volume", "iscsi": "target", "tls": "name", "vtpm": "name"}
# the usage type a reference by usage means: the type the <secret> gives, or
# for a pool's <auth> the auth type; a disk's encryption passphrase is a volume's
REF_USAGE_TYPES = {"ceph": "ceph", "iscsi": "iscsi", "chap": "iscsi", "passphrase": "volume"}
def value_bytes(usage_type, value):
"""The bytes a value typed on the page stands for: a Ceph key as
`ceph auth get-key` prints it, in base64; any other secret's password or
passphrase as it is typed."""
if usage_type != "ceph":
return value.encode()
try:
return base64.b64decode(value, validate=True)
except ValueError: # not base64, or not ASCII; the message never holds the value
raise util.OperationError(gettext_noop("A Ceph key is base64 text, as `ceph auth get-key` prints it")) from None
def secrets_named(tree, path, by_usage):
"""UUIDs of the secrets the <secret> elements at path name, by UUID or by
usage; by_usage maps (usage type, usage) to a UUID."""
uuids = set()
for ref in tree.xpath(path):
if ref.get("uuid"):
uuids.add(ref.get("uuid"))
else:
ref_type = ref.get("type") or ref.getparent().get("type")
uuids.add(by_usage.get((REF_USAGE_TYPES.get(ref_type), ref.get("usage"))))
return uuids
class wvmSecrets(wvmConnect):
def pool_trees(self):
"""Each pool's name with its definitions, parsed: the live one, and
for a persistent pool the one saved for its next start too, read
whether it is active or not (it may stop meanwhile). A pool gone
meanwhile is left out."""
for pool in self.wvm.listAllStoragePools(0):
try:
xmls = [pool.XMLDesc(0)]
if pool.isPersistent():
xmls.append(pool.XMLDesc(VIR_STORAGE_XML_INACTIVE))
except libvirtError as err:
if err.get_error_code() != VIR_ERR_NO_STORAGE_POOL:
raise
continue
yield pool.name(), [etree.fromstring(xml) for xml in xmls]
def get_users(self, secrets):
"""The VMs and pools that name each of secrets (get_secrets_info), by
UUID: a VM's disks, host devices and vTPM, a pool's source, in any of
their definitions. Not seen: a LUKS volume's own encryption, the TLS
settings of qemu.conf, snapshots."""
by_usage = {(secret["usage_type"], secret["usage"]): secret["uuid"] for secret in secrets}
users = {}
def add(kind, name, uuids):
for uuid in uuids - {None}:
users.setdefault(uuid, {"vms": [], "pools": []})[kind].append(name)
for name, trees in self.domain_trees():
uuids = set()
for tree in trees:
uuids.update(secrets_named(tree, "devices//secret", by_usage))
uuids.update(tree.xpath("devices/tpm/backend/encryption/@secret"))
add("vms", name, uuids)
for name, trees in self.pool_trees():
uuids = set()
for tree in trees:
uuids.update(secrets_named(tree, "source//secret", by_usage))
add("pools", name, uuids)
return users
def create_secret(self, usage_type, usage, value="", ephemeral=False, private=False):
"""A secret for usage, with value if one is given: a Ceph key that is
not base64 defines nothing, and a value libvirt refuses undefines the
new secret again."""
value = value_bytes(usage_type, value) if value else None
# built with lxml: the usage is text, whatever characters it holds
secret = etree.Element("secret", ephemeral="yes" if ephemeral else "no", private="yes" if private else "no")
usage_element = etree.SubElement(secret, "usage", type=usage_type)
etree.SubElement(usage_element, USAGE_ELEMENTS[usage_type]).text = usage
defined = self.wvm.secretDefineXML(etree.tostring(secret, encoding="unicode"))
if value is not None:
try:
defined.setValue(value)
except libvirtError:
defined.undefine()
raise
def set_secret_value(self, uuid, value):
"""value as typed on the page; its bytes follow the secret's usage
type (value_bytes)."""
secret = self.wvm.secretLookupByUUIDString(uuid)
usage_type = etree.fromstring(secret.XMLDesc(0)).xpath("string(usage/@type)")
secret.setValue(value_bytes(usage_type, value))
def delete_secret(self, uuid):
"""Refused while a VM or a pool names it (get_users): without it that
VM or pool would no longer start."""
secret = self.wvm.secretLookupByUUIDString(uuid)
if users := self.get_users(self.get_secrets_info()).get(uuid):
named = []
if users["vms"]:
named.append(util.message(gettext_noop("VMs %(names)s"), names=", ".join(users["vms"])))
if users["pools"]:
named.append(util.message(gettext_noop("pools %(names)s"), names=", ".join(users["pools"])))
raise util.OperationError(gettext_noop("Secret %(uuid)s is used by %(named)s; remove it from them first"), uuid=uuid, named=named)
secret.undefine()