Skip to content
Snippets Groups Projects
Commit d009cc14 authored by Lars Seipel's avatar Lars Seipel
Browse files

runner/libvirt: add RunnerOption WriteSSHConfig

The option configures the Runner to write out an OpenSSH
client configuration file.
parent daa8ae0c
No related branches found
No related tags found
No related merge requests found
......@@ -16,7 +16,10 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"inet.af/netaddr"
"libvirt.org/libvirt-go"
"slrz.net/runtopo/topology"
)
......@@ -253,3 +256,42 @@ func isCumulusFunction(f topology.DeviceFunction) bool {
}
return false
}
// Waits until d received a DHCP lease from a libvirt network and return its
// address.
func waitForLease(ctx context.Context, d *libvirt.Domain) (ip netaddr.IP, err error) {
defer func() {
if err != nil {
err = fmt.Errorf("waitForLease: %w", err)
}
}()
var intf libvirt.DomainInterface
for {
xs, err1 := d.ListAllInterfaceAddresses(
libvirt.DOMAIN_INTERFACE_ADDRESSES_SRC_LEASE,
)
if err1 != nil {
err = err1
break
}
if len(xs) > 0 {
intf = xs[0]
break
}
select {
case <-ctx.Done():
return netaddr.IP{}, ctx.Err()
case <-time.After(100 * time.Millisecond):
}
}
if err != nil {
return netaddr.IP{}, err
}
if len(intf.Addrs) == 0 {
return netaddr.IP{}, fmt.Errorf(
"interface %s: no addresses (hwaddr=%s)",
intf.Name, intf.Hwaddr)
}
return netaddr.ParseIP(intf.Addrs[0].Addr)
}
package libvirt
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"net"
"net/url"
"os"
......@@ -20,10 +22,11 @@ import (
// Runner implements the topology.Runner interface using libvirt/qemu.
type Runner struct {
conn *libvirt.Connect
devices map[string]*device
domains map[string]*libvirt.Domain
baseImages map[string]*libvirt.StorageVol
conn *libvirt.Connect
devices map[string]*device
domains map[string]*libvirt.Domain
baseImages map[string]*libvirt.StorageVol
sshConfigOut io.Writer
// fields below are immutable after initialization
uri string // libvirt connection URI
......@@ -114,6 +117,14 @@ func WithAuthorizedKeys(keys ...string) RunnerOption {
}
}
// WriteSSHConfig configures the Runner to write an OpenSSH client
// configuration file to w. See ssh_config(5) for a description of its format.
func WriteSSHConfig(w io.Writer) RunnerOption {
return func(r *Runner) {
r.sshConfigOut = w
}
}
// NewRunner constructs a runner configured with the specified options.
func NewRunner(opts ...RunnerOption) *Runner {
r := &Runner{
......@@ -176,6 +187,13 @@ func (r *Runner) Run(ctx context.Context, t *topology.T) (err error) {
return err
}
if r.sshConfigOut != nil {
// Caller asked us to write out an ssh_config.
if err := r.writeSSHConfig(ctx, t); err != nil {
return err
}
}
return nil
}
......@@ -590,6 +608,51 @@ func (r *Runner) startDomains(ctx context.Context, t *topology.T) (err error) {
return nil
}
// WriteSSHConfig genererates an OpenSSH client config and writes it to r.sshConfigOut.
func (r *Runner) writeSSHConfig(ctx context.Context, t *topology.T) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("writeSSHConfig: %w", err)
}
}()
// Retrieve the mgmt server's DHCP lease as we're going to use
// it as a jump host.
ip, err := waitForLease(ctx, r.domains[r.namePrefix+"oob-mgmt-server"])
if err != nil {
return err
}
w := bufio.NewWriter(r.sshConfigOut)
fmt.Fprintf(w, `Host oob-mgmt-server
Hostname %s
User root
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
`, ip)
for _, d := range t.Devices() {
if d.Function() == topology.FunctionOOBServer ||
d.Function() == topology.FunctionOOBSwitch {
continue
}
user := "root"
if isCumulusFunction(d.Function()) {
user = "cumulus"
}
fmt.Fprintf(w, `Host %s
User %s
ProxyJump oob-mgmt-server
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
`, d.Name, user)
}
return w.Flush()
}
// internal representation for a device
type device struct {
name string
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment