package render import ( "errors" "fmt" "net" "path/filepath" "strings" "git.g3e.fr/syonad/two/internal/lab/topology" ) const ( QEMUBinary = "qemu-system-x86_64" AdminInterface = topology.ReservedInterface AdminAddress = "10.0.2.15/24" AdminGateway = "10.0.2.2" AdminDNS = "10.0.2.3" DiskFile = "disk.qcow2" SeedFile = "seed.iso" ConsoleFile = "console.log" QMPFile = "qmp.sock" PIDFile = "qemu.pid" ) var Nameservers = []string{"1.1.1.1", "8.8.8.8"} type Options struct { RunDir string AuthorizedKeys []string } type Node struct { Name string Dir string QEMU []string MetaData []byte UserData []byte NetworkConfig []byte } func Render(p *topology.Plan, o Options) ([]Node, error) { if err := o.validate(); err != nil { return nil, err } var nodes []Node for index, n := range p.Nodes { dir := filepath.Join(o.RunDir, n.Name) meta, err := metaData(p, n) if err != nil { return nil, err } user, err := userData(p, n, o.AuthorizedKeys) if err != nil { return nil, err } network, err := networkConfig(p, n, index) if err != nil { return nil, err } nodes = append(nodes, Node{ Name: n.Name, Dir: dir, QEMU: qemuArgs(p, n, index, dir), MetaData: meta, UserData: user, NetworkConfig: network, }) } return nodes, nil } func (o Options) validate() error { var errs []error if !filepath.IsAbs(o.RunDir) { errs = append(errs, fmt.Errorf("run dir %q must be an absolute path", o.RunDir)) } if len(o.AuthorizedKeys) == 0 { errs = append(errs, errors.New("at least one authorized ssh key is required")) } for i, k := range o.AuthorizedKeys { if strings.ContainsAny(k, "\r\n") { errs = append(errs, fmt.Errorf("authorized key %d spans several lines", i+1)) continue } fields := strings.Fields(k) if len(fields) < 2 || !validKeyType(fields[0]) { errs = append(errs, fmt.Errorf("authorized key %d is not an ssh public key", i+1)) } } return errors.Join(errs...) } func validKeyType(t string) bool { return strings.HasPrefix(t, "ssh-") || strings.HasPrefix(t, "ecdsa-sha2-") || strings.HasPrefix(t, "sk-") } func adminMAC(index int) net.HardwareAddr { return net.HardwareAddr{0x02, 0x4d, byte(index >> 8), byte(index), 0x00, 0x00} } func nodeCables(p *topology.Plan, name string) []topology.Cable { var out []topology.Cable for _, c := range p.Cables { if c.Node == name { out = append(out, c) } } return out } func switchCables(p *topology.Plan, name string) []topology.Cable { var out []topology.Cable for _, c := range p.Cables { if c.Switch == name { out = append(out, c) } } return out } func switchSegments(p *topology.Plan, name string) []topology.SegmentPlan { var out []topology.SegmentPlan for _, s := range p.Segments { if s.Switch == name { out = append(out, s) } } return out }