misc improvements

This commit is contained in:
Finn 2024-12-02 22:57:21 -08:00
parent 966730080c
commit e88bf31634
9 changed files with 143 additions and 55 deletions

View file

@ -1,11 +1,38 @@
package config
import (
"io"
"os"
"path/filepath"
"gopkg.in/yaml.v2"
"git.janky.solutions/finn/conflib"
)
type Config struct {
DiskStoragePool string `default:"default"`
Network string `default:"default"`
StoragePool string `default:"default" yaml:"storage_pool"`
Network string `yaml:"network"`
Bridge string `yaml:"bridge"`
}
var C = Config{
DiskStoragePool: "default",
Network: "default",
var C Config
func Load() (err error) {
loader := conflib.Loader[Config]{AllowNoSources: true}
C, err = loader.Load(filepath.Join(os.Getenv("HOME"), ".config", "mkvm.yaml"))
if err != nil {
return err
}
if C.Network == "" && C.Bridge == "" {
C.Network = "default"
}
return nil
}
func init() {
conflib.RegisterParser(".yaml", func(target any, f io.Reader) error { return yaml.NewDecoder(f).Decode(target) })
}

2
go.mod
View file

@ -4,6 +4,7 @@ go 1.22.7
require (
entanglement.garden/common v0.0.0-20241203042542-56a48a4bcde1
git.janky.solutions/finn/conflib v0.1.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
@ -16,6 +17,5 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/sys v0.13.0 // indirect
)

2
go.sum
View file

@ -1,5 +1,7 @@
entanglement.garden/common v0.0.0-20241203042542-56a48a4bcde1 h1:a07WE+jnYZA7klXRwBzeI6ZCcYebIBuksH/bwjvS3J0=
entanglement.garden/common v0.0.0-20241203042542-56a48a4bcde1/go.mod h1:LEJBK4jjAkrW+BWqE/EUnZ1oRj+UmJwPMPMEO8OdBds=
git.janky.solutions/finn/conflib v0.1.0 h1:1KFPNmTrhnSL4pbrIOY7Cc0fjenn6tynaYtpbA6T8iU=
git.janky.solutions/finn/conflib v0.1.0/go.mod h1:c2485cJeouzKvX6urOhk4MOENteRl/iIQtR8jKDDuk0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View file

@ -15,6 +15,7 @@ var (
)
func runHTTPServer(bind string) {
logrus.WithField("bind", bind).Debug("starting temporary HTTP server")
http.ListenAndServe(bind, httphandler{})
}

112
main.go
View file

@ -2,24 +2,41 @@ package main
import (
"fmt"
"io"
"math/rand"
"net"
"net/http"
"strings"
"sync"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"libvirt.org/go/libvirtxml"
"mkvm/config"
"mkvm/libvirtx"
)
var (
wg sync.WaitGroup
rootCmd = cobra.Command{
wg sync.WaitGroup
nicSource = libvirtxml.DomainInterfaceSource{}
rootCmd = cobra.Command{
Use: "mkvm name [name [name]]",
Short: "create virtual machine(s) via libvirt",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
logrus.SetLevel(logrus.DebugLevel)
if err := config.Load(); err != nil {
logrus.WithError(err).Fatal("error loading config")
}
for _, u := range argSSHKeyURLs {
if err := downloadSSHKeys(u); err != nil {
logrus.WithError(err).WithField("url", u).Fatal("error downloading SSH keys")
}
}
conn, err := libvirtx.New()
if err != nil {
logrus.WithError(err).Fatal("error connecting to libvirt")
@ -27,12 +44,51 @@ var (
}
defer conn.Close()
network, err := getNetwork(conn)
if err != nil {
logrus.WithError(err).Fatal("error finding requested network")
serverInterfaceName := ""
if config.C.Network != "" {
nicSource.Network = &libvirtxml.DomainInterfaceSourceNetwork{Network: config.C.Network}
libvirtnet, err := conn.LookupNetworkByName(config.C.Network)
if err != nil {
logrus.WithError(err).WithField("network", config.C.Network).Fatal("error finding libvirt network")
}
xmlstr, err := libvirtnet.GetXMLDesc(0)
if err != nil {
logrus.WithError(err).WithField("network", config.C.Network).Fatal("error getting network xml description")
}
var net libvirtxml.Network
if err := net.Unmarshal(xmlstr); err != nil {
logrus.WithError(err).WithField("network", config.C.Network).Fatal("error parsing network xml description")
}
serverInterfaceName = net.Bridge.Name
} else if config.C.Bridge != "" {
nicSource.Bridge = &libvirtxml.DomainInterfaceSourceBridge{Bridge: config.C.Bridge}
serverInterfaceName = config.C.Bridge
} else {
logrus.Fatal("no network or bridge configured")
}
serverInterface, err := net.InterfaceByName(serverInterfaceName)
if err != nil {
logrus.WithError(err).Fatal("error finding local network interface to run server on")
}
serverInterfaceAddrs, err := serverInterface.Addrs()
if err != nil {
logrus.WithError(err).Fatal("error finding local network interface's IP")
}
if len(serverInterfaceAddrs) == 0 {
logrus.WithField("interface", serverInterfaceName).Fatal("bridge interface does not have an IP on this machine")
}
serverBindIP, _, err := net.ParseCIDR(serverInterfaceAddrs[0].String())
if err != nil {
logrus.WithField("interface", serverInterfaceName).WithField("address", serverInterfaceAddrs[0].String()).WithError(err).Fatal("error parsing local address")
}
serverBindIP := network.IPs[0].Address
port := rand.Intn(65535-1025) + 1025
serverBind := fmt.Sprintf("%s:%d", serverBindIP, port)
@ -63,10 +119,47 @@ var (
argDiskSizeGB int
// cloudinit args
argSSHKeys []string
argPackages []string
argSSHKeys []string
argSSHKeyURLs []string
argPackages []string
)
func downloadSSHKeys(url string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
logrus.WithFields(logrus.Fields{
"url": url,
"status": resp.Status,
"body": string(body),
}).Error("non-200 response from SSH key URL")
return fmt.Errorf("non-200 response from SSH key URL: %s", resp.Status)
}
count := 0
for _, key := range strings.Split(string(body), "\n") {
key = strings.TrimSpace(key)
if key == "" {
continue
}
argSSHKeys = append(argSSHKeys, key)
count++
}
logrus.WithField("url", url).WithField("keys", count).Debug("downloaded SSH authorized keys")
return nil
}
func main() {
if err := rootCmd.Execute(); err != nil {
panic(err)
@ -79,6 +172,7 @@ func init() {
rootCmd.Flags().StringVarP(&argImage, "image", "", "https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2", "URL of the image to download")
rootCmd.Flags().IntVarP(&argDiskSizeGB, "disk", "d", 25, "disk size (in GB)")
rootCmd.Flags().StringArrayVarP(&argSSHKeys, "ssh-keys", "s", nil, "SSH key(s) authorzed to access the VM")
rootCmd.Flags().StringArrayVar(&argSSHKeys, "ssh-keys", nil, "SSH key(s) authorzed to access the VM")
rootCmd.Flags().StringArrayVarP(&argSSHKeyURLs, "ssh-key-urls", "s", nil, "URL(s) to SSH key(s) authorzed to access the VM. Expected in authorized_keys format.")
rootCmd.Flags().StringArrayVarP(&argPackages, "packages", "p", nil, "packages to install on the VM")
}

View file

@ -7,7 +7,6 @@ import (
"libvirt.org/go/libvirt"
"libvirt.org/go/libvirtxml"
"mkvm/config"
"mkvm/volumes"
"mkvm/volumes/pools"
)
@ -45,11 +44,8 @@ func mkvm(conn *libvirt.Connect, metadataURL string, name string) error {
interfaces := []libvirtxml.DomainInterface{
{
Model: &libvirtxml.DomainInterfaceModel{Type: "virtio"},
// MAC: &libvirtxml.DomainInterfaceMAC{Address: nic.Mac},
Source: &libvirtxml.DomainInterfaceSource{
Network: &libvirtxml.DomainInterfaceSourceNetwork{Network: config.C.Network},
},
Model: &libvirtxml.DomainInterfaceModel{Type: "virtio"},
Source: &nicSource,
},
}

View file

@ -1,30 +0,0 @@
package main
import (
"encoding/xml"
"mkvm/config"
"libvirt.org/go/libvirt"
"libvirt.org/go/libvirtxml"
)
func getNetwork(conn *libvirt.Connect) (libvirtxml.Network, error) {
var net libvirtxml.Network
libvirtnet, err := conn.LookupNetworkByName(config.C.Network)
if err != nil {
return net, err
}
xmlstr, err := libvirtnet.GetXMLDesc(0)
if err != nil {
return net, err
}
if err := xml.Unmarshal([]byte(xmlstr), &net); err != nil {
return net, err
}
return net, nil
}

View file

@ -1,8 +1,6 @@
package pools
import (
"fmt"
"libvirt.org/go/libvirt"
"libvirt.org/go/libvirtxml"
)
@ -52,7 +50,7 @@ func (p GenericPool) DeleteVolume(name string) error {
}
func (p GenericPool) GetVolumeName(name string) string {
return fmt.Sprintf("rhyzome-%s", name)
return name
}
func (p GenericPool) ResizeVolume(name string, newDiskSizeGB uint64) error {

View file

@ -30,7 +30,7 @@ var drivers = map[string]func(*libvirt.StoragePool) (StoragePool, error){}
// GetPool retrieves the configured Storage Pool from libvirt
func GetPool(conn *libvirt.Connect) (StoragePool, error) {
pool, err := conn.LookupStoragePoolByName(config.C.DiskStoragePool)
pool, err := conn.LookupStoragePoolByName(config.C.StoragePool)
if err != nil {
return nil, err
}