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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user