Files
kacerrandClaude Fable 5.1 7aea14fbb3 Add KVM provider pure builders: domain XML, volumes, seed ISO, metadata
Introduces internal/provider/kvm with the side-effect-free pieces of the
provider: providerID format/parse, the namespaced <metadata> element that
carries the GC contract, q35 domain XML over pool volumes, volume XML for
overlays and raw seeds, the NoCloud seed ISO built with kdomanski/iso9660,
and config defaults. All unit-tested.

Step 2 of docs/plans/2026-09-04-1404-kvm-provider.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:26:17 +00:00

79 lines
2.0 KiB
Go

package kvm
import (
"encoding/xml"
"fmt"
)
const bytesPerGiB = 1 << 30
func overlayVolumeName(name string) string { return name + ".qcow2" }
func seedVolumeName(name string) string { return name + "-seed.iso" }
// volumeSpec describes a storage volume to create. A non-empty BackingPath
// makes it a copy-on-write overlay of that file.
type volumeSpec struct {
Name string
SizeBytes int64
Format string
BackingPath string
BackingFormat string
}
func overlayVolume(name string, sizeGB int64, basePath, baseFormat string) volumeSpec {
return volumeSpec{
Name: overlayVolumeName(name),
SizeBytes: sizeGB * bytesPerGiB,
Format: formatQcow2,
BackingPath: basePath,
BackingFormat: baseFormat,
}
}
func rawVolume(name string, sizeBytes int64) volumeSpec {
return volumeSpec{Name: name, SizeBytes: sizeBytes, Format: formatRaw}
}
// buildVolumeXML renders the volume for StorageVolCreateXML. Capacity is
// always given in bytes so no libvirt unit-suffix ambiguity (G vs GiB) can
// creep in.
func buildVolumeXML(v volumeSpec) (string, error) {
x := volumeXML{
Type: "file",
Name: v.Name,
Capacity: sizeXML{Unit: "bytes", Value: v.SizeBytes},
Target: volumeTargetXML{Format: formatXML{Type: v.Format}},
}
if v.BackingPath != "" {
x.BackingStore = &backingStoreXML{Path: v.BackingPath, Format: formatXML{Type: v.BackingFormat}}
}
out, err := xml.Marshal(x)
if err != nil {
return "", fmt.Errorf("marshalling volume xml: %w", err)
}
return string(out), nil
}
type volumeXML struct {
XMLName xml.Name `xml:"volume"`
Type string `xml:"type,attr"`
Name string `xml:"name"`
Capacity sizeXML `xml:"capacity"`
Target volumeTargetXML `xml:"target"`
BackingStore *backingStoreXML `xml:"backingStore"`
}
type volumeTargetXML struct {
Format formatXML `xml:"format"`
}
type formatXML struct {
Type string `xml:"type,attr"`
}
type backingStoreXML struct {
Path string `xml:"path"`
Format formatXML `xml:"format"`
}