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,91 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kdomanski/iso9660"
|
||||
)
|
||||
|
||||
func readSeed(t *testing.T, iso []byte) (label string, files map[string]string) {
|
||||
t.Helper()
|
||||
img, err := iso9660.OpenImage(bytes.NewReader(iso))
|
||||
if err != nil {
|
||||
t.Fatalf("OpenImage() error = %v", err)
|
||||
}
|
||||
label, err = img.Label()
|
||||
if err != nil {
|
||||
t.Fatalf("Label() error = %v", err)
|
||||
}
|
||||
root, err := img.RootDir()
|
||||
if err != nil {
|
||||
t.Fatalf("RootDir() error = %v", err)
|
||||
}
|
||||
children, err := root.GetChildren()
|
||||
if err != nil {
|
||||
t.Fatalf("GetChildren() error = %v", err)
|
||||
}
|
||||
files = make(map[string]string, len(children))
|
||||
for _, c := range children {
|
||||
body, err := io.ReadAll(c.Reader())
|
||||
if err != nil {
|
||||
t.Fatalf("reading %s: %v", c.Name(), err)
|
||||
}
|
||||
files[c.Name()] = string(body)
|
||||
}
|
||||
return label, files
|
||||
}
|
||||
|
||||
func TestBuildSeedISO_contents(t *testing.T) {
|
||||
t.Parallel()
|
||||
userData := "#cloud-config\npackages: [squid]\n"
|
||||
iso, err := buildSeedISO("proxy-abc123def456ghij", userData, defaultVendorData)
|
||||
if err != nil {
|
||||
t.Fatalf("buildSeedISO() error = %v", err)
|
||||
}
|
||||
label, files := readSeed(t, iso)
|
||||
if !strings.EqualFold(label, seedLabel) {
|
||||
t.Errorf("label = %q, want %q", label, seedLabel)
|
||||
}
|
||||
if len(files) != 3 {
|
||||
t.Errorf("files = %v, want exactly user-data, meta-data, vendor-data", files)
|
||||
}
|
||||
if files["user-data"] != userData {
|
||||
t.Errorf("user-data = %q, want verbatim cloud-init", files["user-data"])
|
||||
}
|
||||
if files["vendor-data"] != defaultVendorData {
|
||||
t.Errorf("vendor-data = %q", files["vendor-data"])
|
||||
}
|
||||
if got, want := files["meta-data"], "instance-id: proxy-abc123def456ghij\nlocal-hostname: proxy-abc123def456ghij\n"; got != want {
|
||||
t.Errorf("meta-data = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSeedISO_deterministic(t *testing.T) {
|
||||
t.Parallel()
|
||||
a, err := buildSeedISO("proxy-x", "#cloud-config\n", "")
|
||||
if err != nil {
|
||||
t.Fatalf("first buildSeedISO() error = %v", err)
|
||||
}
|
||||
b, err := buildSeedISO("proxy-x", "#cloud-config\n", "")
|
||||
if err != nil {
|
||||
t.Fatalf("second buildSeedISO() error = %v", err)
|
||||
}
|
||||
if !bytes.Equal(a, b) {
|
||||
t.Error("two builds for the same inputs differ; a re-created seed must be byte-identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSeedISO_emptyUserData(t *testing.T) {
|
||||
t.Parallel()
|
||||
iso, err := buildSeedISO("proxy-x", "", defaultVendorData)
|
||||
if err != nil {
|
||||
t.Fatalf("buildSeedISO() error = %v", err)
|
||||
}
|
||||
_, files := readSeed(t, iso)
|
||||
if body, ok := files["user-data"]; !ok || body != "" {
|
||||
t.Errorf("user-data = %q (present=%v), want an empty file to still exist", body, ok)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user