From cc6195f3a249c62bb7c2a7abe640b567cffd4bd5 Mon Sep 17 00:00:00 2001
From: Kristian Klausen <kristian@klausen.dk>
Date: Sun, 27 Oct 2024 16:11:46 +0100
Subject: [PATCH] gitlab_runner: Remove tight coupling to libvirt filesystem
 pool

All libvirt volume management is now handled through virsh instead of
direct file system access. As a volume cannot be uploaded in an atomic
way, the current active volume is now tracked in a file on disk.

This may allow us to run the script with less privileges and use polkit
for libvirt access control[1].

[1] https://libvirt.org/aclpolkit.html
---
 roles/gitlab_runner/files/libvirt-executor     | 15 +++++++--------
 .../files/libvirt-executor-update-base-image   | 13 ++++++++-----
 roles/libvirt/files/images.xml                 |  6 ++++++
 roles/libvirt/tasks/main.yml                   | 18 ++++++++++++++++++
 4 files changed, 39 insertions(+), 13 deletions(-)
 create mode 100644 roles/libvirt/files/images.xml

diff --git a/roles/gitlab_runner/files/libvirt-executor b/roles/gitlab_runner/files/libvirt-executor
index a51a14f69..91f1423de 100755
--- a/roles/gitlab_runner/files/libvirt-executor
+++ b/roles/gitlab_runner/files/libvirt-executor
@@ -1,6 +1,6 @@
 #!/usr/bin/env bash
 set -o nounset -o errexit -o pipefail
-readonly libvirt_default_pool_path="/var/lib/libvirt/images"
+readonly libvirt_pool="images"
 
 ssh() {
   command ssh \
@@ -50,15 +50,15 @@ wait_for_ssh() {
 prepare() {
   # shellcheck disable=SC2064
   trap "exit ${SYSTEM_FAILURE_EXIT_CODE:-1}" ERR
-  local base_image
-  base_image="$(compgen -G "${libvirt_default_pool_path}/runner-base-*.qcow2" | sort -n -t - -k3,3 | tail -n 1)"
 
-  if [[ -z ${base_image} ]]; then
-    echo 'Base image not found...'
+  if [[ ! -f /usr/local/lib/libvirt-executor/backing-vol-name ]]; then
+    echo 'Backing volume not found...'
     exit "${SYSTEM_FAILURE_EXIT_CODE:-1}"
   fi
+  local backing_volume
+  backing_volume="$(</usr/local/lib/libvirt-executor/backing-vol-name)"
 
-  qemu-img create -f qcow2 -b "${base_image}" -F qcow2 "${libvirt_default_pool_path}/$(vm_name).qcow2"
+  virsh vol-create-as "${libvirt_pool}" "$(vm_name).qcow2" 0 --format qcow2 --backing-vol "${backing_volume}" --backing-vol-format qcow2
   virsh define <(sed "s/\$vm_name/$(vm_name)/" /usr/local/lib/libvirt-executor/domain_template.xml)
   virsh start "$(vm_name)"
 
@@ -81,8 +81,7 @@ run() {
 # https://docs.gitlab.com/runner/executors/custom.html#cleanup
 cleanup() {
   virsh destroy "$(vm_name)" || true
-  rm "${libvirt_default_pool_path}/$(vm_name).qcow2"
-  virsh undefine "$(vm_name)"
+  virsh undefine --remove-all-storage "$(vm_name)"
 }
 
 case "${1:-}" in
diff --git a/roles/gitlab_runner/files/libvirt-executor-update-base-image b/roles/gitlab_runner/files/libvirt-executor-update-base-image
index 1f96ecaff..9a743c871 100755
--- a/roles/gitlab_runner/files/libvirt-executor-update-base-image
+++ b/roles/gitlab_runner/files/libvirt-executor-update-base-image
@@ -1,6 +1,6 @@
 #!/bin/bash
 set -o nounset -o errexit
-readonly libvirt_default_pool_path="/var/lib/libvirt/images"
+readonly libvirt_pool="images"
 readonly arch_boxes_signing_key=/usr/local/lib/libvirt-executor/arch-boxes.asc
 readonly arch_boxes_fingerprint=1B9A16984A4E8CB448712D2AE0B78BF4326C6F8F
 
@@ -56,9 +56,12 @@ losetup -d "${loopdev}"
 loopdev=""
 
 qemu-img convert -f raw -O qcow2 Arch-Linux-x86_64-basic.img Arch-Linux-x86_64-basic.qcow2
-printf -v image_path '%s/runner-base-%(%s)T.qcow2' "${libvirt_default_pool_path}"
-cp Arch-Linux-x86_64-basic.qcow2 "${image_path}.tmp"
-mv "${image_path}"{.tmp,}
+printf -v vol_name 'runner-base-%(%s)T.qcow2'
+virsh vol-create-as "${libvirt_pool}" "${vol_name}" 0 --format qcow2
+virsh vol-upload "${vol_name}" Arch-Linux-x86_64-basic.qcow2 "${libvirt_pool}"
+
+echo "${vol_name}" > /usr/local/lib/libvirt-executor/backing-vol-name.tmp
+mv /usr/local/lib/libvirt-executor/backing-vol-name{.tmp,}
 
 # Keep one week of base images
-compgen -G "${libvirt_default_pool_path}/runner-base-*.qcow2" | sort -n -t - -k3,3 | head -n -7 | xargs --no-run-if-empty rm -vf
+virsh vol-list "${libvirt_pool}" | awk '$1~"runner-base-[0-9]*\\.qcow2" {print $1}' | sort -n -t - -k3,3 | head -n -7 | xargs -I{} --no-run-if-empty virsh vol-delete {} "${libvirt_pool}"
diff --git a/roles/libvirt/files/images.xml b/roles/libvirt/files/images.xml
new file mode 100644
index 000000000..6222186e1
--- /dev/null
+++ b/roles/libvirt/files/images.xml
@@ -0,0 +1,6 @@
+<pool type='dir'>
+  <name>images</name>
+  <target>
+    <path>/var/lib/libvirt/images</path>
+  </target>
+</pool>
diff --git a/roles/libvirt/tasks/main.yml b/roles/libvirt/tasks/main.yml
index ae8863b51..9307576fb 100644
--- a/roles/libvirt/tasks/main.yml
+++ b/roles/libvirt/tasks/main.yml
@@ -12,6 +12,8 @@
       - qemu-base
       - qemu-hw-display-virtio-gpu
       - qemu-hw-display-virtio-vga
+      - libvirt-python
+      - python-lxml
   register: result
 
 - name: Reload firewalld
@@ -23,3 +25,19 @@
 
 - name: Start and enable libvirtd
   systemd: name=libvirtd enabled=yes state=started daemon_reload=yes
+
+- name: Define the images storage pool
+  community.libvirt.virt_pool:
+    command: define
+    name: images
+    xml: "{{ lookup('file', 'images.xml') }}"
+
+- name: Start the image storage pool
+  community.libvirt.virt_pool:
+    state: active
+    name: images
+
+- name: Start the image storage pool at boot
+  community.libvirt.virt_pool:
+    autostart: true
+    name: images
-- 
GitLab