diff --git a/virtsecrets/templates/create_secret_block.html b/virtsecrets/templates/create_secret_block.html
index 9f8ed5bf..34b7003d 100644
--- a/virtsecrets/templates/create_secret_block.html
+++ b/virtsecrets/templates/create_secret_block.html
@@ -46,8 +46,7 @@
diff --git a/virtsecrets/tests/__init__.py b/virtsecrets/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/virtsecrets/tests/test_create_secret.py b/virtsecrets/tests/test_create_secret.py
new file mode 100644
index 00000000..7ed716db
--- /dev/null
+++ b/virtsecrets/tests/test_create_secret.py
@@ -0,0 +1,28 @@
+"""A secret's usage value goes into its XML as text, whatever it holds: a
+volume path, an iSCSI IQN or a Ceph name, and characters XML gives a meaning."""
+
+from unittest.mock import patch
+
+from django.test import SimpleTestCase
+from lxml import etree
+from vrtManager.virtsecrets import wvmSecrets
+
+
+class CreateSecretTests(SimpleTestCase):
+ def defined_xml(self, usage_type, data):
+ with patch("vrtManager.connection.connection_manager.get_connection") as get:
+ wvmSecrets("host", "login", "pw", 1).create_secret("no", "yes", usage_type, data)
+ return etree.fromstring(get.return_value.secretDefineXML.call_args.args[0])
+
+ def test_the_usage_value_is_text_of_its_element(self):
+ for usage_type, element, data in (
+ ("volume", "volume", "/var/lib/libvirt/images/vm-1.qcow2"),
+ ("iscsi", "target", "iqn.2026-10.test.wvc:lun"),
+ ("ceph", "name", "client.libvirt secret"),
+ ("volume", "volume", "/a'b&d\""),
+ ):
+ with self.subTest(usage_type=usage_type, data=data):
+ secret = self.defined_xml(usage_type, data)
+ self.assertEqual((secret.get("ephemeral"), secret.get("private")), ("no", "yes"))
+ self.assertEqual(secret.find("usage").get("type"), usage_type)
+ self.assertEqual(secret.findtext(f"usage/{element}"), data)
diff --git a/virtsecrets/tests/test_live_secrets.py b/virtsecrets/tests/test_live_secrets.py
new file mode 100644
index 00000000..f55bc7f4
--- /dev/null
+++ b/virtsecrets/tests/test_live_secrets.py
@@ -0,0 +1,52 @@
+"""Live check that the secrets page creates a secret for each usage type
+with a real usage value: a volume path, an iSCSI IQN and a Ceph name. Runs
+only with TEST_LIBVIRT_HOST set."""
+
+import os
+import unittest
+
+import libvirt
+from computes.models import Compute
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+from django.urls import reverse
+from instances.tests import livetest
+from vrtManager.connection import connection_manager
+
+P = livetest.PREFIX
+USAGES = {
+ libvirt.VIR_SECRET_USAGE_TYPE_VOLUME: ("volume", f"/var/lib/libvirt/{P}secrets/vm-1.qcow2"),
+ libvirt.VIR_SECRET_USAGE_TYPE_ISCSI: ("iscsi", f"iqn.2026-10.test.wvc:{P}secret"),
+ libvirt.VIR_SECRET_USAGE_TYPE_CEPH: ("ceph", f"client.{P}secret secret"),
+}
+
+
+@unittest.skipUnless(livetest.enabled(), "Set TEST_LIBVIRT_HOST to run the live libvirt tests")
+class LiveSecretsTestCase(TestCase):
+ def setUp(self):
+ args = dict(
+ hostname=os.environ["TEST_LIBVIRT_HOST"],
+ login=os.environ.get("TEST_LIBVIRT_LOGIN", ""),
+ password=os.environ.get("TEST_LIBVIRT_PASSWORD", ""),
+ type=int(os.environ.get("TEST_LIBVIRT_TYPE", 4)),
+ )
+ self.conn = connection_manager.get_connection(*args.values())
+ self.remove_secrets()
+ self.addCleanup(self.remove_secrets)
+ self.compute = Compute.objects.create(name="live-secrets", **args)
+ self.client.force_login(get_user_model().objects.create_superuser("live-secrets", "ls@example.com", "x"))
+
+ def remove_secrets(self):
+ for secret in self.conn.listAllSecrets():
+ if (secret.usageType(), secret.usageID()) in {(t, v) for t, (_, v) in USAGES.items()}:
+ secret.undefine()
+
+ def test_each_usage_type_takes_a_real_value(self):
+ for usage_type, (name, value) in USAGES.items():
+ with self.subTest(name):
+ response = self.client.post(
+ reverse("virtsecrets", args=[self.compute.id]),
+ {"create": "", "ephemeral": "no", "private": "no", "usage_type": name, "data": value},
+ )
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(self.conn.secretLookupByUsage(usage_type, value).usageID(), value)
diff --git a/vrtManager/virtsecrets.py b/vrtManager/virtsecrets.py
index c8772823..1117e7e5 100644
--- a/vrtManager/virtsecrets.py
+++ b/vrtManager/virtsecrets.py
@@ -1,21 +1,19 @@
import base64
+from lxml import etree
from vrtManager.connection import wvmConnect
+# the element of that holds the value, per usage type
+USAGE_ELEMENTS = {"ceph": "name", "volume": "volume", "iscsi": "target"}
+
class wvmSecrets(wvmConnect):
def create_secret(self, ephemeral, private, secret_type, data):
- xml = f"""
- """
- if secret_type == "ceph":
- xml += f"""{data}"""
- if secret_type == "volume":
- xml += f"""{data}"""
- if secret_type == "iscsi":
- xml += f"""{data}"""
- xml += """
- """
- self.wvm.secretDefineXML(xml)
+ # built with lxml: data is text, whatever characters it holds
+ secret = etree.Element("secret", ephemeral=ephemeral, private=private)
+ usage = etree.SubElement(secret, "usage", type=secret_type)
+ etree.SubElement(usage, USAGE_ELEMENTS[secret_type]).text = data
+ self.wvm.secretDefineXML(etree.tostring(secret, encoding="unicode"))
def get_secret_value(self, uuid):
secrt = self.get_secret(uuid)