Create looks the domain up first and only starts it when it exists; otherwise it caches the image, reuses or creates the overlay, rebuilds the seed ISO unconditionally (a cancelled upload can leave a truncated seed that no later attempt would repair), defines and starts the domain. Get maps libvirt state plus the first routable IPv4 from ARP or the guest agent onto the provider states. Delete destroys, undefines and removes the per-VM volumes by name, tolerating every already-gone case. ListByTag keeps managed domains across all hosts, skipping domains it cannot read and failing the sweep only for an unreachable host. Also corrects the Step 2 seed determinism test: the iso9660 writer stamps the build time into the volume descriptor, so builds differ by a few bytes; the test now compares label, files and size. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
54 lines
1.7 KiB
Go
54 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
|
|
// cloud-init never mistakes a reboot for a new instance. The ISO's volume
|
|
// descriptor carries the build time, so two builds differ in a few
|
|
// timestamp bytes; the files and label are identical.
|
|
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
|
|
}
|