qemu: add support for using a network bridge
This commit is contained in:
parent
6ad67f6800
commit
06fad6cc4f
|
@ -185,6 +185,16 @@ type Config struct {
|
|||
// `vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
|
||||
// default.
|
||||
NetDevice string `mapstructure:"net_device" required:"false"`
|
||||
// Connects the network to this bridge instead of using the user mode
|
||||
// networking.
|
||||
//
|
||||
// **NB** This bridge must already exist. You can use the `virbr0` bridge
|
||||
// as created by vagrant-libvirt.
|
||||
//
|
||||
// **NB** This will automatically enable the QMP socket (see QMPEnable).
|
||||
//
|
||||
// **NB** This only works in Linux based OSes.
|
||||
NetBridge string `mapstructure:"net_bridge" required:"false"`
|
||||
// This is the path to the directory where the
|
||||
// resulting virtual machine will be created. This may be relative or absolute.
|
||||
// If relative, the path is relative to the working directory when packer
|
||||
|
@ -534,7 +544,16 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
|||
errs, fmt.Errorf("vnc_port_min must be less than vnc_port_max"))
|
||||
}
|
||||
|
||||
if b.config.VNCUsePassword && b.config.QMPSocketPath == "" {
|
||||
if b.config.NetBridge != "" && runtime.GOOS != "linux" {
|
||||
errs = packer.MultiErrorAppend(
|
||||
errs, fmt.Errorf("net_bridge is only supported in Linux based OSes"))
|
||||
}
|
||||
|
||||
if b.config.NetBridge != "" || b.config.VNCUsePassword {
|
||||
b.config.QMPEnable = true
|
||||
}
|
||||
|
||||
if b.config.QMPEnable && b.config.QMPSocketPath == "" {
|
||||
socketName := fmt.Sprintf("%s.monitor", b.config.VMName)
|
||||
b.config.QMPSocketPath = filepath.Join(b.config.OutputDir, socketName)
|
||||
}
|
||||
|
@ -603,7 +622,7 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
},
|
||||
)
|
||||
|
||||
if b.config.Comm.Type != "none" {
|
||||
if b.config.Comm.Type != "none" && b.config.NetBridge == "" {
|
||||
steps = append(steps,
|
||||
new(stepForwardSSH),
|
||||
)
|
||||
|
@ -613,12 +632,17 @@ func (b *Builder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (pack
|
|||
new(stepConfigureVNC),
|
||||
steprun,
|
||||
&stepConfigureQMP{
|
||||
VNCUsePassword: b.config.VNCUsePassword,
|
||||
QMPSocketPath: b.config.QMPSocketPath,
|
||||
QMPSocketPath: b.config.QMPSocketPath,
|
||||
},
|
||||
&stepTypeBootCommand{},
|
||||
)
|
||||
|
||||
if b.config.Comm.Type != "none" && b.config.NetBridge != "" {
|
||||
steps = append(steps,
|
||||
new(stepWaitGuestAddress),
|
||||
)
|
||||
}
|
||||
|
||||
if b.config.Comm.Type != "none" {
|
||||
steps = append(steps,
|
||||
&communicator.StepConnect{
|
||||
|
|
|
@ -96,6 +96,7 @@ type FlatConfig struct {
|
|||
MachineType *string `mapstructure:"machine_type" required:"false" cty:"machine_type"`
|
||||
MemorySize *int `mapstructure:"memory" required:"false" cty:"memory"`
|
||||
NetDevice *string `mapstructure:"net_device" required:"false" cty:"net_device"`
|
||||
NetBridge *string `mapstructure:"net_bridge" required:"false" cty:"net_bridge"`
|
||||
OutputDir *string `mapstructure:"output_directory" required:"false" cty:"output_directory"`
|
||||
QemuArgs [][]string `mapstructure:"qemuargs" required:"false" cty:"qemuargs"`
|
||||
QemuBinary *string `mapstructure:"qemu_binary" required:"false" cty:"qemu_binary"`
|
||||
|
@ -212,6 +213,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec {
|
|||
"machine_type": &hcldec.AttrSpec{Name: "machine_type", Type: cty.String, Required: false},
|
||||
"memory": &hcldec.AttrSpec{Name: "memory", Type: cty.Number, Required: false},
|
||||
"net_device": &hcldec.AttrSpec{Name: "net_device", Type: cty.String, Required: false},
|
||||
"net_bridge": &hcldec.AttrSpec{Name: "net_bridge", Type: cty.String, Required: false},
|
||||
"output_directory": &hcldec.AttrSpec{Name: "output_directory", Type: cty.String, Required: false},
|
||||
"qemuargs": &hcldec.AttrSpec{Name: "qemuargs", Type: cty.List(cty.List(cty.String)), Required: false},
|
||||
"qemu_binary": &hcldec.AttrSpec{Name: "qemu_binary", Type: cty.String, Required: false},
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/digitalocean/go-qemu/qmp"
|
||||
)
|
||||
|
||||
type qomListRequest struct {
|
||||
Execute string `json:"execute"`
|
||||
Arguments qomListRequestArguments `json:"arguments"`
|
||||
}
|
||||
|
||||
type qomListRequestArguments struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type qomListResponse struct {
|
||||
Return []qomListReturn `json:"return"`
|
||||
}
|
||||
|
||||
type qomListReturn struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func qmpQomList(qmpMonitor *qmp.SocketMonitor, path string) ([]qomListReturn, error) {
|
||||
request, _ := json.Marshal(qomListRequest{
|
||||
Execute: "qom-list",
|
||||
Arguments: qomListRequestArguments{
|
||||
Path: path,
|
||||
},
|
||||
})
|
||||
result, err := qmpMonitor.Run(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response qomListResponse
|
||||
if err := json.Unmarshal(result, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Return, nil
|
||||
}
|
||||
|
||||
type qomGetRequest struct {
|
||||
Execute string `json:"execute"`
|
||||
Arguments qomGetRequestArguments `json:"arguments"`
|
||||
}
|
||||
|
||||
type qomGetRequestArguments struct {
|
||||
Path string `json:"path"`
|
||||
Property string `json:"property"`
|
||||
}
|
||||
|
||||
type qomGetResponse struct {
|
||||
Return string `json:"return"`
|
||||
}
|
||||
|
||||
func qmpQomGet(qmpMonitor *qmp.SocketMonitor, path string, property string) (string, error) {
|
||||
request, _ := json.Marshal(qomGetRequest{
|
||||
Execute: "qom-get",
|
||||
Arguments: qomGetRequestArguments{
|
||||
Path: path,
|
||||
Property: property,
|
||||
},
|
||||
})
|
||||
result, err := qmpMonitor.Run(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var response qomGetResponse
|
||||
if err := json.Unmarshal(result, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.Return, nil
|
||||
}
|
||||
|
||||
type netDevice struct {
|
||||
Path string
|
||||
Name string
|
||||
Type string
|
||||
MacAddress string
|
||||
}
|
||||
|
||||
func getNetDevices(qmpMonitor *qmp.SocketMonitor) ([]netDevice, error) {
|
||||
devices := []netDevice{}
|
||||
for _, parentPath := range []string{"/machine/peripheral", "/machine/peripheral-anon"} {
|
||||
listResponse, err := qmpQomList(qmpMonitor, parentPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get qmp qom list %v: %w", parentPath, err)
|
||||
}
|
||||
for _, p := range listResponse {
|
||||
if strings.HasPrefix(p.Type, "child<") {
|
||||
path := fmt.Sprintf("%s/%s", parentPath, p.Name)
|
||||
r, err := qmpQomList(qmpMonitor, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get qmp qom list %v: %w", path, err)
|
||||
}
|
||||
isNetdev := false
|
||||
for _, d := range r {
|
||||
if d.Name == "netdev" {
|
||||
isNetdev = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isNetdev {
|
||||
device := netDevice{
|
||||
Path: path,
|
||||
}
|
||||
for _, d := range r {
|
||||
if d.Name != "type" && d.Name != "netdev" && d.Name != "mac" {
|
||||
continue
|
||||
}
|
||||
value, err := qmpQomGet(qmpMonitor, path, d.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get qmp qom property %v %v: %w", path, d.Name, err)
|
||||
}
|
||||
switch d.Name {
|
||||
case "type":
|
||||
device.Type = value
|
||||
case "netdev":
|
||||
device.Name = value
|
||||
case "mac":
|
||||
device.MacAddress = value
|
||||
}
|
||||
}
|
||||
devices = append(devices, device)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return devices, nil
|
||||
}
|
|
@ -13,11 +13,18 @@ func commHost(host string) func(multistep.StateBag) (string, error) {
|
|||
return host, nil
|
||||
}
|
||||
|
||||
if guestAddress, ok := state.Get("guestAddress").(string); ok {
|
||||
return guestAddress, nil
|
||||
}
|
||||
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
}
|
||||
|
||||
func commPort(state multistep.StateBag) (int, error) {
|
||||
sshHostPort := state.Get("sshHostPort").(int)
|
||||
sshHostPort, ok := state.Get("sshHostPort").(int)
|
||||
if !ok {
|
||||
sshHostPort = 22
|
||||
}
|
||||
return int(sshHostPort), nil
|
||||
}
|
||||
|
|
|
@ -20,15 +20,15 @@ import (
|
|||
//
|
||||
// Produces:
|
||||
type stepConfigureQMP struct {
|
||||
monitor *qmp.SocketMonitor
|
||||
VNCUsePassword bool
|
||||
QMPSocketPath string
|
||||
monitor *qmp.SocketMonitor
|
||||
QMPSocketPath string
|
||||
}
|
||||
|
||||
func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if !s.VNCUsePassword {
|
||||
if !config.QMPEnable {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
|
@ -46,12 +46,10 @@ func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
QMPMonitor := s.monitor
|
||||
vncPassword := state.Get("vnc_password")
|
||||
|
||||
// Connect to QMP
|
||||
// function automatically calls capabilities so is immediately ready for commands
|
||||
err = QMPMonitor.Connect()
|
||||
err = s.monitor.Connect()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
|
@ -60,21 +58,22 @@ func (s *stepConfigureQMP) Run(ctx context.Context, state multistep.StateBag) mu
|
|||
}
|
||||
log.Printf("QMP socket open SUCCESS")
|
||||
|
||||
cmd = []byte(fmt.Sprintf("{ \"execute\": \"change-vnc-password\", \"arguments\": { \"password\": \"%s\" } }",
|
||||
vncPassword))
|
||||
result, err = QMPMonitor.Run(cmd)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
vncPassword := state.Get("vnc_password")
|
||||
if vncPassword != "" {
|
||||
cmd = []byte(fmt.Sprintf("{ \"execute\": \"change-vnc-password\", \"arguments\": { \"password\": \"%s\" } }",
|
||||
vncPassword))
|
||||
result, err = s.monitor.Run(cmd)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error connecting to QMP socket: %s", err)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
log.Printf("QMP Command: %s\nResult: %s", cmd, result)
|
||||
}
|
||||
|
||||
log.Printf("QMP Command: %s\nResult: %s", cmd, result)
|
||||
|
||||
// Put QMP monitor in statebag in case there is a use in a following step
|
||||
// Uncomment for future case as it is unused for now
|
||||
//state.Put("qmp_monitor", QMPMonitor)
|
||||
// make the qmp_monitor available to other steps.
|
||||
state.Put("qmp_monitor", s.monitor)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -2,8 +2,11 @@ package qemu
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// Step to discover the http ip
|
||||
|
@ -12,7 +15,52 @@ import (
|
|||
type stepHTTPIPDiscover struct{}
|
||||
|
||||
func (s *stepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
state.Put("http_ip", "10.0.2.2")
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
hostIP := ""
|
||||
|
||||
if config.NetBridge == "" {
|
||||
hostIP = "10.0.2.2"
|
||||
} else {
|
||||
bridgeInterface, err := net.InterfaceByName(config.NetBridge)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting the bridge %s interface: %s", config.NetBridge, err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
addrs, err := bridgeInterface.Addrs()
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error getting the bridge %s interface addresses: %s", config.NetBridge, err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
hostIP = ip.String()
|
||||
break
|
||||
}
|
||||
if hostIP == "" {
|
||||
err := fmt.Errorf("Error getting an IPv4 address from the bridge %s: cannot find any IPv4 address", config.NetBridge)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
state.Put("http_ip", hostIP)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
func TestStepHTTPIPDiscover_Run(t *testing.T) {
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("ui", &packer.BasicUi{
|
||||
Reader: new(bytes.Buffer),
|
||||
Writer: new(bytes.Buffer),
|
||||
})
|
||||
config := &Config{}
|
||||
state.Put("config", config)
|
||||
step := new(stepHTTPIPDiscover)
|
||||
hostIp := "10.0.2.2"
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
|
@ -79,16 +80,24 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
vnc = fmt.Sprintf("%s:%d", vncIP, vncPort-5900)
|
||||
} else {
|
||||
vnc = fmt.Sprintf("%s:%d,password", vncIP, vncPort-5900)
|
||||
}
|
||||
|
||||
if config.QMPEnable {
|
||||
defaultArgs["-qmp"] = fmt.Sprintf("unix:%s,server,nowait", config.QMPSocketPath)
|
||||
}
|
||||
|
||||
defaultArgs["-name"] = vmName
|
||||
defaultArgs["-machine"] = fmt.Sprintf("type=%s", config.MachineType)
|
||||
if config.Comm.Type != "none" {
|
||||
sshHostPort = state.Get("sshHostPort").(int)
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
|
||||
|
||||
if config.NetBridge == "" {
|
||||
if config.Comm.Type != "none" {
|
||||
sshHostPort = state.Get("sshHostPort").(int)
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0,hostfwd=tcp::%v-:%d", sshHostPort, config.Comm.Port())
|
||||
} else {
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
||||
}
|
||||
} else {
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("user,id=user.0")
|
||||
defaultArgs["-netdev"] = fmt.Sprintf("bridge,id=user.0,br=%s", config.NetBridge)
|
||||
}
|
||||
|
||||
rawVersion, err := driver.Version()
|
||||
|
@ -215,11 +224,12 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
if len(config.QemuArgs) > 0 {
|
||||
ui.Say("Overriding defaults Qemu arguments with QemuArgs...")
|
||||
|
||||
httpIp := common.GetHTTPIP()
|
||||
httpPort := state.Get("http_port").(int)
|
||||
ictx := config.ctx
|
||||
if config.Comm.Type != "none" {
|
||||
ictx.Data = qemuArgsTemplateData{
|
||||
"10.0.2.2",
|
||||
httpIp,
|
||||
httpPort,
|
||||
config.HTTPDir,
|
||||
config.OutputDir,
|
||||
|
@ -228,7 +238,7 @@ func getCommandArgs(bootDrive string, state multistep.StateBag) ([]string, error
|
|||
}
|
||||
} else {
|
||||
ictx.Data = qemuArgsTemplateData{
|
||||
HTTPIP: "10.0.2.2",
|
||||
HTTPIP: httpIp,
|
||||
HTTPPort: httpPort,
|
||||
HTTPDir: config.HTTPDir,
|
||||
OutputDir: config.OutputDir,
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
package qemu
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
||||
"github.com/digitalocean/go-qemu/qmp"
|
||||
)
|
||||
|
||||
// This step waits for the guest address to become available in the network
|
||||
// bridge, then it sets the guestAddress state property.
|
||||
type stepWaitGuestAddress struct{}
|
||||
|
||||
func (s *stepWaitGuestAddress) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
qmpMonitor := state.Get("qmp_monitor").(*qmp.SocketMonitor)
|
||||
|
||||
ui.Say(fmt.Sprintf("Waiting for the guest address to become available in the %s network bridge...", config.NetBridge))
|
||||
for {
|
||||
guestAddress := getGuestAddress(qmpMonitor, config.NetBridge, "user.0")
|
||||
if guestAddress != "" {
|
||||
log.Printf("Found guest address %s", guestAddress)
|
||||
state.Put("guestAddress", guestAddress)
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepWaitGuestAddress) Cleanup(state multistep.StateBag) {
|
||||
}
|
||||
|
||||
func getGuestAddress(qmpMonitor *qmp.SocketMonitor, bridgeName string, deviceName string) string {
|
||||
devices, err := getNetDevices(qmpMonitor)
|
||||
if err != nil {
|
||||
log.Printf("Could not retrieve QEMU QMP network device list: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
if device.Name == deviceName {
|
||||
ipAddress, _ := getDeviceIPAddress(bridgeName, device.MacAddress)
|
||||
return ipAddress
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("QEMU QMP network device %s was not found", deviceName)
|
||||
return ""
|
||||
}
|
||||
|
||||
func getDeviceIPAddress(device string, macAddress string) (string, error) {
|
||||
// this parses /proc/net/arp to retrieve the given device IP address.
|
||||
//
|
||||
// /proc/net/arp is normally someting alike:
|
||||
//
|
||||
// IP address HW type Flags HW address Mask Device
|
||||
// 192.168.121.111 0x1 0x2 52:54:00:12:34:56 * virbr0
|
||||
//
|
||||
|
||||
const (
|
||||
IPAddressIndex int = iota
|
||||
HWTypeIndex
|
||||
FlagsIndex
|
||||
HWAddressIndex
|
||||
MaskIndex
|
||||
DeviceIndex
|
||||
)
|
||||
|
||||
// see ARP flags at https://github.com/torvalds/linux/blob/v5.4/include/uapi/linux/if_arp.h#L132
|
||||
const (
|
||||
AtfCom int = 0x02 // ATF_COM (complete)
|
||||
)
|
||||
|
||||
f, err := os.Open("/proc/net/arp")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open /proc/net/arp: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
s := bufio.NewScanner(f)
|
||||
s.Scan()
|
||||
|
||||
for s.Scan() {
|
||||
fields := strings.Fields(s.Text())
|
||||
|
||||
if device != "" && fields[DeviceIndex] != device {
|
||||
continue
|
||||
}
|
||||
|
||||
if fields[HWAddressIndex] != macAddress {
|
||||
continue
|
||||
}
|
||||
|
||||
flags, err := strconv.ParseInt(fields[FlagsIndex], 0, 32)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse /proc/net/arp flags field %s: %w", fields[FlagsIndex], err)
|
||||
}
|
||||
|
||||
if int(flags)&AtfCom == AtfCom {
|
||||
return fields[IPAddressIndex], nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("could not find %s", macAddress)
|
||||
}
|
|
@ -108,6 +108,16 @@
|
|||
`vmxnet3`, `i82558a` or `i82558b`. The Qemu builder uses `virtio-net` by
|
||||
default.
|
||||
|
||||
- `net_bridge` (string) - Connects the network to this bridge instead of using the user mode
|
||||
networking.
|
||||
|
||||
**NB** This bridge must already exist. You can use the `virbr0` bridge
|
||||
as created by vagrant-libvirt.
|
||||
|
||||
**NB** This will automatically enable the QMP socket (see QMPEnable).
|
||||
|
||||
**NB** This only works in Linux based OSes.
|
||||
|
||||
- `output_directory` (string) - This is the path to the directory where the
|
||||
resulting virtual machine will be created. This may be relative or absolute.
|
||||
If relative, the path is relative to the working directory when packer
|
||||
|
|
Loading…
Reference in New Issue