Skip to content

Commit

Permalink
feat: add support for http_interface and http_bind_address
Browse files Browse the repository at this point in the history
Adds support for `http_interface` and `http_bind_address`.

Signed-off-by: Ryan Johnson <[email protected]>
  • Loading branch information
tenthirtyam committed May 30, 2024
1 parent 8b55d18 commit dd847dc
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 111 deletions.
29 changes: 21 additions & 8 deletions .web-docs/components/builder/vsphere-clone/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -773,14 +773,6 @@ For more examples of various boot commands, see the sample projects from our
<!-- End of code generated from the comments of the BootConfig struct in bootcommand/config.go; -->


<!-- Code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; DO NOT EDIT MANUALLY -->

- `http_ip` (string) - The IP address to use for the HTTP server started to serve the `http_directory`.
If unset, Packer will automatically discover and assign an IP.

<!-- End of code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; -->


### HTTP Directory Configuration

<!-- Code generated from the comments of the HTTPConfig struct in multistep/commonsteps/http_config.go; DO NOT EDIT MANUALLY -->
Expand Down Expand Up @@ -839,6 +831,27 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
<!-- End of code generated from the comments of the HTTPConfig struct in multistep/commonsteps/http_config.go; -->


- `http_interface` (string) - The network interface (for example, `en0`, `ens192`, etc.) that the
HTTP server will use to serve the `http_directory`. The plugin will identify the IP address
associated with this network interface and bind to it.

<!-- Code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; DO NOT EDIT MANUALLY -->

- `http_ip` (string) - The IP address to use for the HTTP server to serve the `http_directory`.

<!-- End of code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; -->


~> **Notes:**
- The options `http_bind_address` and `http_interface` are mutually exclusive.
- Both `http_bind_address` and `http_interface` have higher priority than `http_ip`.
- The `http_bind_address` is matched against the IP addresses of the host's network interfaces. If
no match is found, the plugin will terminate.
- Similarly, `http_interface` is compared with the host's network interfaces. If there's no
corresponding network interface, the plugin will also terminate.
- If neither `http_bind_address`, `http_interface`, and `http_ip` are provided, the plugin will
automatically find and use the IP address of the first non-loopback interface for `http_ip`.

### Floppy Configuration

**Optional:**
Expand Down
29 changes: 21 additions & 8 deletions .web-docs/components/builder/vsphere-iso/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,27 @@ wget http://{{ .HTTPIP }}:{{ .HTTPPort }}/foo/bar/preseed.cfg
<!-- End of code generated from the comments of the HTTPConfig struct in multistep/commonsteps/http_config.go; -->


- `http_interface` (string) - The network interface (for example, `en0`, `ens192`, etc.) that the
HTTP server will use to serve the `http_directory`. The plugin will identify the IP address
associated with this network interface and bind to it.

<!-- Code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; DO NOT EDIT MANUALLY -->

- `http_ip` (string) - The IP address to use for the HTTP server to serve the `http_directory`.

<!-- End of code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; -->


~> **Notes:**
- The options `http_bind_address` and `http_interface` are mutually exclusive.
- Both `http_bind_address` and `http_interface` have higher priority than `http_ip`.
- The `http_bind_address` is matched against the IP addresses of the host's network interfaces. If
no match is found, the plugin will terminate.
- Similarly, `http_interface` is compared with the host's network interfaces. If there's no
corresponding network interface, the plugin will also terminate.
- If neither `http_bind_address`, `http_interface`, and `http_ip` are provided, the plugin will
automatically find and use the IP address of the first non-loopback interface for `http_ip`.

### Connection Configuration

**Optional**:
Expand Down Expand Up @@ -1087,14 +1108,6 @@ JSON Example:
<!-- End of code generated from the comments of the BootConfig struct in bootcommand/config.go; -->


<!-- Code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; DO NOT EDIT MANUALLY -->

- `http_ip` (string) - The IP address to use for the HTTP server started to serve the `http_directory`.
If unset, Packer will automatically discover and assign an IP.

<!-- End of code generated from the comments of the BootConfig struct in builder/vsphere/common/step_boot_command.go; -->


### Wait Configuration

**Optional**:
Expand Down
55 changes: 51 additions & 4 deletions builder/vsphere/clone/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package clone
import (
"context"
"fmt"
"net"

"github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer-plugin-sdk/communicator"
Expand Down Expand Up @@ -93,10 +94,26 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
Host: b.config.Host,
SetHostForDatastoreUploads: b.config.SetHostForDatastoreUploads,
},
&common.StepHTTPIPDiscover{
HTTPIP: b.config.BootConfig.HTTPIP,
Network: b.config.WaitIpConfig.GetIPNet(),
},
)

if b.config.HTTPConfig.HTTPAddress != "" && b.config.HTTPConfig.HTTPAddress != "0.0.0.0" {
if !isIPInInterfaces(b.config.HTTPConfig.HTTPAddress) {
ui.Error(fmt.Sprintf("'http_bind_address' %s is not assigned to any interface", b.config.HTTPConfig.HTTPAddress))
return nil, fmt.Errorf("'http_bind_address' %s is not assigned to any interface", b.config.HTTPConfig.HTTPAddress)
}
state.Put("http_bind_address", b.config.HTTPConfig.HTTPAddress)
} else if b.config.HTTPConfig.HTTPInterface != "" {
state.Put("http_interface", b.config.HTTPConfig.HTTPInterface)
} else {
steps = append(steps,
&common.StepHTTPIPDiscover{
HTTPIP: b.config.BootConfig.HTTPIP,
Network: b.config.WaitIpConfig.GetIPNet(),
},
)
}

steps = append(steps,
commonsteps.HTTPServerFromHTTPConfig(&b.config.HTTPConfig),
&common.StepSshKeyPair{
Debug: b.config.PackerDebug,
Expand Down Expand Up @@ -197,3 +214,33 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
}
return artifact, nil
}

func isIPInInterfaces(ipStr string) bool {
interfaces, err := net.Interfaces()
if err != nil {
return false
}

for _, i := range interfaces {
addrs, err := i.Addrs()
if err != nil {
continue
}

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.String() == ipStr {
return true
}
}
}

return false
}
132 changes: 51 additions & 81 deletions builder/vsphere/common/step_boot_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/hashicorp/packer-plugin-sdk/bootcommand"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/driver"
Expand All @@ -22,8 +21,7 @@ import (

type BootConfig struct {
bootcommand.BootConfig `mapstructure:",squash"`
// The IP address to use for the HTTP server started to serve the `http_directory`.
// If unset, Packer will automatically discover and assign an IP.
// The IP address to use for the HTTP server to serve the `http_directory`.
HTTPIP string `mapstructure:"http_ip"`
}

Expand All @@ -42,30 +40,16 @@ func (c *BootConfig) Prepare(ctx *interpolate.Context) []error {
}

type StepBootCommand struct {
Config *BootConfig
VMName string
Ctx interpolate.Context
HTTPConfig *commonsteps.HTTPConfig
Config *BootConfig
VMName string
Ctx interpolate.Context
}

func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
debug := state.Get("debug").(bool)
ui := state.Get("ui").(packersdk.Ui)
vm := state.Get("vm").(*driver.VirtualMachineDriver)

httpConfig := s.HTTPConfig
bootConfig := s.Config

if httpConfig.HTTPAddress != "" && bootConfig.HTTPIP != "" {
state.Put("error", fmt.Errorf("'http_ip' and 'http_bind_address' cannot be used at the same time"))
return multistep.ActionHalt
}

if httpConfig.HTTPInterface != "" && (httpConfig.HTTPAddress != "" || bootConfig.HTTPIP != "") {
state.Put("error", fmt.Errorf("'http_interface' cannot be used with either 'http_ip' or 'http_bind_address'"))
return multistep.ActionHalt
}

if s.Config.BootCommand == nil {
return multistep.ActionContinue
}
Expand All @@ -86,46 +70,60 @@ func (s *StepBootCommand) Run(ctx context.Context, state multistep.StateBag) mul
pauseFn = state.Get("pauseFn").(multistep.DebugPauseFn)
}

port := state.Get("http_port").(int)
var ip string
var err error
port, ok := state.Get("http_port").(int)
if !ok {
ui.Say(fmt.Sprintf("error retrieving 'http_port' from state"))

Check failure on line 77 in builder/vsphere/common/step_boot_command.go

View workflow job for this annotation

GitHub Actions / Lint check

S1039: unnecessary use of fmt.Sprintf (gosimple)
return multistep.ActionHalt
}

if port > 0 {
var ip string
var err error
if s.HTTPConfig != nil && s.HTTPConfig.HTTPAddress != "" && s.HTTPConfig.HTTPAddress != "0.0.0.0" {
ip = s.HTTPConfig.HTTPAddress
ui.Say(fmt.Sprintf("Using http_bind_address: `%s`", ip))
} else if s.HTTPConfig != nil && s.HTTPConfig.HTTPInterface != "" {
ip, err = hostIP(s.HTTPConfig.HTTPInterface)
if err != nil {
err = fmt.Errorf("error determining IP address from interface: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Using IP address `%s` from interface `%s`", ip, s.HTTPConfig.HTTPInterface))
} else {
ipVal := state.Get("http_ip")
if ipVal != nil {
ip = ipVal.(string)
ui.Say(fmt.Sprintf("Using http_ip: `%s`", ip))
} else {
ip, err = firstNonLoopbackInterface()
if err != nil {
err = fmt.Errorf("error determining ip address from first non-loopback interface: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
keys := []string{"http_bind_address", "http_interface", "http_ip"}

for _, key := range keys {
value, ok := state.Get(key).(string)
if ok && value != "" {
switch key {
case "http_bind_address":
ip = value
ui.Say(fmt.Sprintf("Using IP address %s from 'http_bind_address'.", ip))
case "http_interface":
ip, err = hostIP(value)
if err != nil {
err = fmt.Errorf("error determining IP address from interface %s: %s", value, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Using IP address %s from 'http_interface' %s.", ip, value))
case "http_ip":
ip = value
httpIPSetByUser, ok := state.Get("http_ip_set_by_user").(bool)
if ok && httpIPSetByUser {
ui.Say(fmt.Sprintf("Using IP address %s from 'http_ip'.", ip))
} else {
ui.Say(fmt.Sprintf("Using IP address %s from first non-loopback interface.", ip))
}
}
ui.Say(fmt.Sprintf("Using IP address `%s` from first non-loopback interface", ip))
break
}
}
s.Ctx.Data = &bootCommandTemplateData{
ip,
port,
s.VMName,

if ip == "" {
ui.Error(fmt.Sprintf("error determining IP address"))

Check failure on line 114 in builder/vsphere/common/step_boot_command.go

View workflow job for this annotation

GitHub Actions / Lint check

S1039: unnecessary use of fmt.Sprintf (gosimple)
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Serving HTTP requests at http://%v:%v/", ip, port))
}

s.Ctx.Data = &bootCommandTemplateData{
ip,
port,
s.VMName,
}

ui.Say(fmt.Sprintf("Serving HTTP requests at http://%v:%v/.", ip, port))

var keyAlt, keyCtrl, keyShift bool
sendCodes := func(code key.Code, down bool) error {
switch code {
Expand Down Expand Up @@ -229,31 +227,3 @@ func hostIP(ifname string) (string, error) {
}
return "", errors.New("error returning host ip address")
}

func firstNonLoopbackInterface() (string, error) {
interfaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, i := range interfaces {
if i.Flags&net.FlagLoopback == 0 && i.Flags&net.FlagUp != 0 {
addrs, err := i.Addrs()
if err != nil {
return "", err
}
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 && ip.To4() != nil {
return ip.String(), nil
}
}
}
}
return "", fmt.Errorf("error returning first non-loopback interface")
}
7 changes: 7 additions & 0 deletions builder/vsphere/common/step_http_ip_discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ func (s *StepHTTPIPDiscover) Run(ctx context.Context, state multistep.StateBag)
state.Put("error", err)
return multistep.ActionHalt
}

if s.HTTPIP != "" {
state.Put("http_ip_set_by_user", true)
} else {
state.Put("http_ip_set_by_user", false)
}

state.Put("http_ip", ip)

return multistep.ActionContinue
Expand Down
Loading

0 comments on commit dd847dc

Please sign in to comment.