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 }