The problem you're addressing (if any)
The qubesos Ansible module (as of qubes-ansible 1.2.9 on Qubes R4.3) has no way to perform the equivalent of qvm-shutdown --force. Its state: shutdown path unconditionally calls vm.shutdown() without a force argument, so when the target VM has any running domain connected to it (e.g. a VPN provider with dependent AppVMs, or an smb-bridge VM that itself has dependents), the task aborts with:
qubesadmin.exc.QubesVMInUseError: There are other VMs connected to this VM: <dependent>
This is a common outcome for any role that touches a service/netvm at the end of its play. Existing workarounds are all unattractive:
state: destroyed — not equivalent. That path calls vm.kill(), which SIGKILLs the Xen domain with no graceful shutdown, no flush, no dependent notification. It is strictly more dangerous than qvm-shutdown --force, which at least halts the target gracefully and simply ignores the connected-VMs check.
- Shut dependents down first from Ansible. Requires discovering running dependents via
qvm-ls or qubesadmin, then recursing when those dependents themselves have dependents (chain: AppVM → bridge-VM → netvm). This is a topological sort in YAML — doable, but it's re-implementing in playbook code what the CLI already handles with one flag.
command: qvm-shutdown --force … — works but bypasses the module, meaning we lose the module's wait/timeout semantics, change-reporting, and any future improvements.
The result is that Ansible roles targeting netvms either have to ship a mini topological-shutdown orchestrator, accept a permanent failing task, or silently swap in an unsafe vm.kill() fallback.
The solution you'd like
Add an optional force: bool parameter (default false) to the qubesos module. When force: true is passed alongside state: shutdown (and analogously state: restarted), call vm.shutdown(force=True) instead of vm.shutdown(). This is a thin passthrough — the underlying qubesadmin API already accepts the kwarg, and the CLI wrapper (qvm-shutdown --force) already uses it.
Sketch (in plugins/modules/qubesos.py):
# in argument_spec:
force=dict(type='bool', default=False),
# in the shutdown method:
def shutdown(self, vmname, wait=False, force=False):
vm = self.get_vm(vmname)
with suppress(QubesVMNotStartedError):
vm.shutdown(force=force)
...
# in the state dispatcher:
elif state == "shutdown":
...
v.shutdown(guest, wait=module.params.get("wait", False),
force=module.params.get("force", False))
Docs should be explicit about the semantic difference from state: destroyed:
state: shutdown + force: false (current) — polite shutdown; aborts if connected domains exist.
state: shutdown + force: true (new) — polite shutdown of the target, but the "connected domains" safety check is skipped. The target halts gracefully; dependents keep running without their uplink. Equivalent to qvm-shutdown --force.
state: destroyed (unchanged) — immediate vm.kill() / SIGKILL. No flush, no graceful stop. For use only when the VM is truly wedged.
The value to a user and who that user might be
Anyone automating Qubes infrastructure with Ansible — which is the expected audience of qubes-ansible in the first place. In particular:
- Operators maintaining service-VM chains (netvms, VPN providers, printer relays, smb bridges) where the last step of a role is "reboot the VM so new
/rw/bind-dirs bindings take effect". Today those roles either fail their final task every time a dependent is running, or drop down to raw shell qvm-shutdown --force, losing module semantics. The missing parameter is the single thing preventing a clean, idempotent, single-task netvm reboot in an Ansible role.
- Users managing multi-qube deployments where VM state changes during provisioning (i.e. most real deployments). They currently have to accept
failed=1 on every run or hand-write recursive shutdown logic.
- Contributors who would reasonably assume the module is already feature-par with the CLI. The principle-of-least-surprise gap between
qvm-shutdown --force (exists, documented, common) and qubesos: state: shutdown force: true (doesn't exist) is a real stumbling block when porting shell scripts to roles.
The implementation is small and isolated. The existing state: destroyed code path covers a genuinely different use case (hard kill) and should stay distinct.
Completion criteria checklist
(This section is for developer use only. Please do not modify it.)
The problem you're addressing (if any)
The
qubesosAnsible module (as of qubes-ansible 1.2.9 on Qubes R4.3) has no way to perform the equivalent ofqvm-shutdown --force. Itsstate: shutdownpath unconditionally callsvm.shutdown()without aforceargument, so when the target VM has any running domain connected to it (e.g. a VPN provider with dependent AppVMs, or an smb-bridge VM that itself has dependents), the task aborts with:This is a common outcome for any role that touches a service/netvm at the end of its play. Existing workarounds are all unattractive:
state: destroyed— not equivalent. That path callsvm.kill(), which SIGKILLs the Xen domain with no graceful shutdown, no flush, no dependent notification. It is strictly more dangerous thanqvm-shutdown --force, which at least halts the target gracefully and simply ignores the connected-VMs check.qvm-lsorqubesadmin, then recursing when those dependents themselves have dependents (chain:AppVM → bridge-VM → netvm). This is a topological sort in YAML — doable, but it's re-implementing in playbook code what the CLI already handles with one flag.command: qvm-shutdown --force …— works but bypasses the module, meaning we lose the module's wait/timeout semantics, change-reporting, and any future improvements.The result is that Ansible roles targeting netvms either have to ship a mini topological-shutdown orchestrator, accept a permanent failing task, or silently swap in an unsafe
vm.kill()fallback.The solution you'd like
Add an optional
force: boolparameter (defaultfalse) to thequbesosmodule. Whenforce: trueis passed alongsidestate: shutdown(and analogouslystate: restarted), callvm.shutdown(force=True)instead ofvm.shutdown(). This is a thin passthrough — the underlyingqubesadminAPI already accepts the kwarg, and the CLI wrapper (qvm-shutdown --force) already uses it.Sketch (in
plugins/modules/qubesos.py):Docs should be explicit about the semantic difference from
state: destroyed:state: shutdown+force: false(current) — polite shutdown; aborts if connected domains exist.state: shutdown+force: true(new) — polite shutdown of the target, but the "connected domains" safety check is skipped. The target halts gracefully; dependents keep running without their uplink. Equivalent toqvm-shutdown --force.state: destroyed(unchanged) — immediatevm.kill()/ SIGKILL. No flush, no graceful stop. For use only when the VM is truly wedged.The value to a user and who that user might be
Anyone automating Qubes infrastructure with Ansible — which is the expected audience of qubes-ansible in the first place. In particular:
/rw/bind-dirsbindings take effect". Today those roles either fail their final task every time a dependent is running, or drop down to raw shellqvm-shutdown --force, losing module semantics. The missing parameter is the single thing preventing a clean, idempotent, single-task netvm reboot in an Ansible role.failed=1on every run or hand-write recursive shutdown logic.qvm-shutdown --force(exists, documented, common) andqubesos: state: shutdown force: true(doesn't exist) is a real stumbling block when porting shell scripts to roles.The implementation is small and isolated. The existing
state: destroyedcode path covers a genuinely different use case (hard kill) and should stay distinct.Completion criteria checklist
(This section is for developer use only. Please do not modify it.)