mkvm/debian.go

143 lines
3.8 KiB
Go

package main
import (
"fmt"
"io"
"mkvm/volumes"
"mkvm/volumes/pools"
"net/http"
"strings"
"entanglement.garden/common/cloudinit"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
debianRelease string
debianPackages []string
debianFilenameSuffix = map[pools.ImageFormat]string{
pools.ImageFormatRaw: "-generic-amd64-daily.raw",
pools.ImageFormatQcow2: "-generic-amd64-daily.qcow2",
}
)
var debianCmd = cobra.Command{
Use: "debian vm-name",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
for _, u := range argSSHKeyURLs {
if err := downloadSSHKeys(u); err != nil {
logrus.WithError(err).WithField("url", u).Fatal("error downloading SSH keys")
}
}
conn, pool, err := getLibvirtAndPool()
if err != nil {
logrus.WithError(err).Fatal("error connecting to libvirt")
return
}
defer conn.Close()
diskImageURL, err := debianGetImageURL(debianRelease, pool.ImageFormat())
if err != nil {
logrus.WithError(err).Fatal("error getting disk image URL")
}
// download disk image
err = volumes.Create(conn, pool, argDiskSizeGB, diskImageURL, name)
if err != nil {
logrus.WithError(err).Fatal("error creating VM disk")
}
// prepare cloudconfig and start the http server
bind, err := getServerListenAddress(conn)
if err != nil {
logrus.WithError(err).Fatal("error getting server bind address")
}
serverURL := fmt.Sprintf("http://%s", bind)
cloudconfig := cloudinit.UserData{
Packages: argPackages,
SSHAuthorizedKeys: argSSHKeys,
}
err = buildCloudConfig(cloudconfig, name, fmt.Sprintf("%s/phone-home", serverURL))
if err != nil {
logrus.WithError(err).Fatal("error building cloud config")
}
go runHTTPServer(bind)
smbios := map[int]map[string]string{1: {"serial": fmt.Sprintf("ds=nocloud-net;s=%s/", serverURL)}}
// create domain
err = createDomain(conn, pool, name, setSMBIOS(smbios))
if err != nil {
logrus.WithError(err).Fatal("error creating domain")
}
wg.Add(1)
logrus.Info("waiting for VM to finish provisioning")
wg.Wait()
},
}
func init() {
debianCmd.Flags().StringVarP(&debianRelease, "release", "r", "bookworm", "debian release to install. Options: bookworm (default), trixie, sid")
debianCmd.Flags().StringArrayVarP(&debianPackages, "packages", "p", nil, "apt packages to install")
debianCmd.Flags().StringArrayVar(&argSSHKeys, "ssh-keys", nil, "SSH key(s) authorzed to access the VM")
debianCmd.Flags().StringArrayVarP(&argSSHKeyURLs, "ssh-key-urls", "s", nil, "URL(s) to SSH key(s) authorzed to access the VM. Expected in authorized_keys format.")
registerGlobalFlags(debianCmd)
rootCmd.AddCommand(&debianCmd)
}
func debianGetImageURL(release string, format pools.ImageFormat) (string, error) {
imageSuffix, ok := debianFilenameSuffix[format]
if !ok {
return "", fmt.Errorf("unexpected image format %s from storage pool", format)
}
diskImageURLPrefix := fmt.Sprintf("https://cloud.debian.org/images/cloud/%s/daily/latest", release)
// find image URL + hash
shaURL := fmt.Sprintf("%s/SHA512SUMS", diskImageURLPrefix)
shaResp, err := http.Get(shaURL)
if err != nil {
return "", err
}
defer shaResp.Body.Close()
shas, err := io.ReadAll(shaResp.Body)
if err != nil {
return "", err
}
if shaResp.StatusCode != http.StatusOK {
logrus.WithFields(logrus.Fields{
"status": shaResp.Status,
"url": shaURL,
"resp": string(shas),
}).Fatal("failed to get image hash")
}
for _, line := range strings.Split(string(shas), "\n") {
hash, filename, ok := strings.Cut(strings.TrimSpace(line), " ")
if !ok {
continue
}
if !strings.HasSuffix(filename, imageSuffix) {
continue
}
return fmt.Sprintf("%s/%s#hash=sha512:%s", diskImageURLPrefix, filename, hash), nil
}
return "", fmt.Errorf("unable to find hash of image in %s", shaURL)
}