Skip to content

plugin-vm

Placement compiled-in (in-process)
Source github.com/opencharly/charly/candy/plugin-vm
Version 2026.177.0400
Candy plugin-vm

This plugin is listed in charly/charly.yml’s compiled_plugins:, so its providers are compiled into the charly binary and register in-process.

The reserved words this plugin serves:

  • vm — command class
  • libvirt — verb class

The VM subsystem relocated into a COMPILED-IN plugin candy (P10). It provides TWO capabilities: verb:libvirt — the libvirt live check verb (list, info, screenshot, send-key, QMP, qemu-guest-agent, snapshots, events) plus the internal VM-resolution ops (resolve-target / domain-state / list-domains the host’s spice/vnc/ssh/status/preempt consumers call), dispatched through the provider registry; and command:vm — the charly vm … lifecycle CLI (build / create / start / stop / destroy / console / ssh / snapshot / gpu / import / clone / cp-box / list), COMPILED-IN and dispatched IN-PROC via Invoke(OpRun), so its handlers run in charly’s own process (native stdio/TTY for console/ssh). The handlers own the CLI + the libvirt/qemu engine in-package and reach the host-only Mechanisms over generic seams: the config loader + deploy-ledger READ via HostBuild(“config-resolve”), the deploy-ledger WRITE plugin-side via deploykit.SaveVmDeployState/RemoveVmDeployEntry directly (#55 coneC-dsh β2 — the config-persist host seam is deleted), the VM-disk build engine plugin-side (candy/plugin-vm/vm_build_resolve.go — the former HostBuild(“vm-build”) is deleted), egress via verb:egress, preempt via verb:arbiter, GPU via verb:gpu. All of it is backed by go-libvirt + kata-containers/govmm + libvirt.org/go/libvirtxml, compiled into charly for this canonical placement. The libvirt: verb dispatches through the provider registry exactly like a built-in (the libvirt: <method> sugar desugars to plugin/plugin_input — the method + every libvirt-exclusive modifier ride the input map, validated against this plugin’s own #LibvirtInput; authoring unchanged), running the LibvirtCmd Kong tree IN this plugin’s process.

The CUE schema below is the authoritative grammar for this plugin’s input. It is the same single source that generates the plugin’s Go parameter types and answers the runtime Describe RPC, so this page cannot disagree with either.

// This out-of-tree VM plugin's OWN CUE schema, served over the Describe channel — the
// typed plugin_input for the `libvirt` VM-probe check verb. It is the SINGLE SOURCE
// for this plugin's params, used two ways (the same contract core `spec` and the http
// plugin use):
//
// 1. GENERATE the Go param struct — `cue exp gengotypes` (driven by task cue:gen,
// which wraps this with `package params` + `@go(params)`) emits
// ../params/cue_types_gen.go, so the provider decodes plugin_input into a TYPED
// struct, never a hand-parsed map.
// 2. VALIDATE authored input AT RUNTIME — the plugin serves this source over the
// Describe channel; the host splices it onto the base (base ++ plugin) and
// validates every authored `libvirt:` step's plugin_input against #LibvirtVerbInput.
//
// Since the schema-compaction cutover the per-verb fields left core #Op: a step's
// `libvirt: <method>` sugar desugars to the internal plugin/plugin_input pair, the
// method name rides the input's `method` key (the former core #LibvirtMethod enum),
// and every libvirt-exclusive modifier lives HERE — including `command` (the
// guest/exec argv, ABSORBED from the former shared #Op command modifier). The
// snapshot methods keep requiring the step-level `target:` field — a genuinely
// SHARED #Op modifier that stays on the op, like timeout and the
// exit_status/stdout/stderr matchers.
//
// SELF-CONTAINED: it references NO base def, so it compiles standalone (the SDK's
// serve-side check + gengotypes) AND splices onto the base (base ++ plugin is a
// def-name collision check, not a base-reference resolver).
//
// The plugin ALSO serves command:vm (`charly vm …`, the externalized VM lifecycle
// CLI) — the command parses its own args out-of-process and carries NO plugin_input,
// so no input def for it lives here.
// #LibvirtVerbInput is the `libvirt` verb's plugin_input: the method name plus its
// method-exclusive modifiers.
#LibvirtVerbInput: {
// method — the libvirt method name (the former core #LibvirtMethod enum; the
// verb's PRIMARY input field, so `libvirt: info` desugars to {method: "info"}).
method: ("list" | "info" | "screenshot" | "send-key" | "passwd" | "qmp" | "domain-xml" | "console" | "events" | "guest/ping" | "guest/info" | "guest/os-info" | "guest/time" | "guest/hostname" | "guest/users" | "guest/interfaces" | "guest/disks" | "guest/fsinfo" | "guest/vcpus" | "guest/exec" | "guest/fstrim" | "snapshot/list" | "snapshot/create" | "snapshot/info" | "snapshot/revert" | "snapshot/delete") @go(Method,type=string)
// text — passwd's new graphics password / qmp's command name.
text?: string
// input — qmp's optional JSON args blob.
input?: string
// key — send-key's key/chord spec (whitespace-split into keycode slots).
key?: string @go(KeyName)
// command — guest/exec's argv (whitespace-split; absorbed from the former
// shared #Op command modifier — authored `command:` on a step is the command
// plugin's sugar key now).
command?: string
// uri — libvirt connection URI override ("" → qemu:///session; the nested CLI
// also honours CHARLY_LIBVIRT_URI).
uri?: string @go(URI)
// artifact + validators — screenshot's PNG output path and the post-run
// artifact-reality assertions (sdk.RunArtifactValidators reads them off the input).
artifact?: string
artifact_min_bytes?: int & >=0 @go(ArtifactMinBytes,type=int)
artifact_min_dimensions?: string & =~"^[0-9]+x[0-9]+$" @go(ArtifactMinDimensions)
artifact_not_uniform?: bool @go(ArtifactNotUniform)
artifact_min_cast_events?: int & >=0 @go(ArtifactMinCastEvents,type=int)
}

See also the candy reference for this candy’s install surface.