I’m currently trying to make my Qubes setup reproducible using salt.
Before I get to my current issue, I should point out that as a new user, salt has been nothing but a very painful experience so far. I’m not saying this to rant against salt - I just think first-hand feedback and experience from new users might be of interest. I’ve spent the last 10 hours wading through forum posts, scattered docs, github repositories, asking AI assistants - all to perform a seemingly very basic task, to absolutely no avail.
Here’s how I would sum it up in a nutshell: if I want to reproduce something that someone has already done (e.g. follow exactly a tutorial, copy a state from someone else), then Salt works fine - and once it works, it’s great. But if I want to create something a bit different, and the first try does not work, then it’s hell understanding why it doesn’t work, what the canonical way to do it is, and what the potential error message is even trying to tell me.
My actual issue:
To reduce tech debt and code duplication (and preserve my sanity), I needed a way to easily create a VM from a template from a base template.
Since afaik states can’t accept arguments, I created a macro.
# Creates[if not existing] a template from a base[will be pulled if necessary]
{% macro ensure_template_from_base(tpl_name, tpl_base, color, ns) %}
{{tpl_name}}-ensure-base-template:
qvm.template_installed:
- name: {{tpl_base}}
{% if ns.require %}
- require:
- {{ns.require}}
{% endif %}
{{tpl_name}}-create-template:
qvm.clone:
- name: {{tpl_name}}
- source: {{tpl_base}}
- require:
- qvm: {{tpl_name}}-ensure-base-template
{{tpl_name}}-configure-template:
qvm.prefs:
- name: {{tpl_name}}
- label: {{color}}
- require:
- qvm: {{tpl_name}}-create-template
{% set ns.require = 'qvm: ' ~ tpl_name ~ '-configure-template' %}
{% endmacro %}
# Creates[if not existing] a template+vm from a base[will be pulled if necessary]
{% macro ensure_template_and_vm_from_base(tpl_name, vm_name, tpl_base, color, ns) %}
{{ ensure_template_from_base(tpl_name, tpl_base, color, ns) }}
{{vm_name}}:
qvm.vm:
- present:
- template: {{tpl_name}}
- label: {{color}}
- prefs:
- template: {{tpl_name}}
- label: {{color}}
require:
- {{ns.require}}
{% set ns.require = 'qvm: ' ~ vm_name %}
{% endmacro %}
A few side-comments:
- I didn’t find any canonical way to combine macros with
require, e.g. to make a state require the “last” state in a macro without either hardcoding the last state’s name (
), or passing a namespace nsto every single macro, which greatly adds to the boilerplate. - The hard-requirement that state IDs be unique is a huge pain. I have every reason to want to create a “local” state e.g.
configure-vm-prefsin different macros, which sets a given subset of the vm’s prefs. In any other language this would be a breeze, I feel like I’m missing some piece of the puzzle here. Appending the vm’s name to the state, a painful “solution”, is not even enough - I may want the macro to take a subset of the prefs only, and then name management becomes a nightmare.
This macro is then called e.g. from
{% from 'custom_salt/lib.sls' import ensure_template_and_vm_from_base %}
{% set ns = namespace(require=None) %}
# ---- Config
{% set tpl_base = 'debian-13-xfce' %}
{% set vm_name = tpl_base ~ '-gpu' %}
{% set tpl_name = vm_name ~ '-t' %}
{% set color = 'green' %}
{% set pci_list = ['00_01.0-00_00.0', '00_01.0-00_00.1'] %}
# ----------------
{% if grains['id'] == 'dom0' %}
{{ ensure_template_and_vm_from_base(tpl_name, vm_name, tpl_base, color, ns) }}
configure-gpu-template:
qvm.prefs:
- name: {{tpl_name}}
- vcpus: 6
- memory: 4000
- maxmem: 0
- virt_mode: hvm
- kernel: ''
- require:
- qvm: {{tpl_name}}
{{vm_name}}-prefs:
qvm.prefs:
- name: {{vm_name}}
- memory: 16000
- maxmem: 0
- pcidevs: {{ pci_list | json }}
- require:
- qvm: {{vm_name}}
{% elif grains['id'] == tpl_name %}
enable-non-free:
cmd.run:
- name: sed -i 's/non-free-firmware/& non-free/' /etc/apt/sources.list
- unless: grep -q "non-free-firmware non-free" /etc/apt/sources.list
nvidia-package-install:
pkg.installed:
- pkgs:
- linux-headers-amd64
- nvidia-open-kernel-dkms
- nvidia-driver
- require:
- cmd: enable-non-free
On this setup, qubesctl state.highstate will fail with
...
----------
ID: configure-gpu-template
Function: qvm.prefs
Name: debian-13-xfce-gpu-t
Result: False
Comment: Recursive requisite found
Changes:
----------
ID: debian-13-xfce-gpu-prefs
Function: qvm.prefs
Name: debian-13-xfce-gpu
Result: False
Comment: Recursive requisite found
Changes:
...
I have no clue where the requisite is recursive: both states simply require that their VM/template exists.
From my understanding, I’m asking for an extremely basic requirement flow: create a template, create a vm, and update their prefs once they’re respectively created. (Clearly, the lack of any detail in the error message doesn’t help either…)
Note: (I think it’s important to insist on that point): I’m not trying to blame anything/anyone on this - it’s quite likely that the problem here lies between the chair and the computer - but I consider myself decently proficient, and have spent (I think) quite some time trying to understand what is going on to no avail - if this happens to me, it likely happens in similar fashion to other users who will just get discouraged and give up on salt. I’m writing what I encountered to 1) (major) share my experience as a new user, which regular users of this forum might not be aware of and 2) (minor) to find a solution to my technical problem.
Thank you