Skip to content

qubes-ansible: add force parameter to qubesos module's state: shutdown #10856

Description

@jmynes

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:

  1. 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.
  2. 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.
  3. 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.)

Metadata

Metadata

Assignees

Labels

C: AnsibleThis issue pertains to the use of Ansible in Qubes OS.P: defaultPriority: default. Default priority for new issues, to be replaced given sufficient information.pr submittedA pull request has been submitted for this issue.

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions