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>
53 lines
1.7 KiB
Go
53 lines
1.7 KiB
Go
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
|
|
}
|