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,52 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/kdomanski/iso9660"
|
||||
)
|
||||
|
||||
// seedLabel is the volume label cloud-init's NoCloud datasource looks for.
|
||||
const seedLabel = "cidata"
|
||||
|
||||
// defaultVendorData is the cloud-init vendor-data shipped on every seed
|
||||
// unless the config overrides it. cloud-init merges it under the Proxy's own
|
||||
// user-data, so stock images grow a guest agent (which lets the provider
|
||||
// read the VM's IP before ARP sees traffic) without the user's cloud-init
|
||||
// having to know about it.
|
||||
const defaultVendorData = `#cloud-config
|
||||
packages:
|
||||
- qemu-guest-agent
|
||||
runcmd:
|
||||
- [systemctl, enable, --now, qemu-guest-agent]
|
||||
`
|
||||
|
||||
// buildSeedISO renders a NoCloud seed: user-data verbatim, vendor-data as
|
||||
// given, and a meta-data whose instance-id is the deterministic VM name, so a
|
||||
// re-created seed for the same name is byte-identical and cloud-init never
|
||||
// mistakes a reboot for a new instance.
|
||||
func buildSeedISO(name, userData, vendorData string) ([]byte, error) {
|
||||
w, err := iso9660.NewWriter()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating iso writer: %w", err)
|
||||
}
|
||||
defer w.Cleanup() //nolint:errcheck // temp-dir cleanup; nothing to do on failure
|
||||
|
||||
files := []struct{ name, body string }{
|
||||
{"user-data", userData},
|
||||
{"meta-data", fmt.Sprintf("instance-id: %s\nlocal-hostname: %s\n", name, name)},
|
||||
{"vendor-data", vendorData},
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := w.AddFile(strings.NewReader(f.body), f.name); err != nil {
|
||||
return nil, fmt.Errorf("adding %s to seed iso: %w", f.name, err)
|
||||
}
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := w.WriteTo(&buf, seedLabel); err != nil {
|
||||
return nil, fmt.Errorf("writing seed iso: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user