From 1fb22a7b7b8255480e908933249b78175c57f937 Mon Sep 17 00:00:00 2001 From: Joshua Foster Date: Thu, 4 Jun 2020 01:35:29 -0400 Subject: [PATCH 1/5] add the ability to check if the IP is within the an IP range --- builder/vsphere/clone/config.hcl2spec.go | 2 ++ builder/vsphere/common/step_wait_for_ip.go | 17 +++++++-- .../common/step_wait_for_ip.hcl2spec.go | 2 ++ builder/vsphere/driver/vm.go | 36 +++++++++++++++++-- builder/vsphere/driver/vm_clone_acc_test.go | 2 +- builder/vsphere/iso/config.hcl2spec.go | 2 ++ .../common/WaitIpConfig-not-required.mdx | 5 ++- 7 files changed, 60 insertions(+), 6 deletions(-) diff --git a/builder/vsphere/clone/config.hcl2spec.go b/builder/vsphere/clone/config.hcl2spec.go index 4d3936e78..d0c7d2c00 100644 --- a/builder/vsphere/clone/config.hcl2spec.go +++ b/builder/vsphere/clone/config.hcl2spec.go @@ -51,6 +51,7 @@ type FlatConfig struct { BootOrder *string `mapstructure:"boot_order" cty:"boot_order" hcl:"boot_order"` WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"` SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"` + WaitAddress *string `mapstructure:"ip_wait_address" cty:"ip_wait_address" hcl:"ip_wait_address"` Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` @@ -154,6 +155,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "boot_order": &hcldec.AttrSpec{Name: "boot_order", Type: cty.String, Required: false}, "ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false}, "ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false}, + "ip_wait_address": &hcldec.AttrSpec{Name: "ip_wait_address", Type: cty.String, Required: false}, "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, diff --git a/builder/vsphere/common/step_wait_for_ip.go b/builder/vsphere/common/step_wait_for_ip.go index d5f0db772..6c33b887c 100644 --- a/builder/vsphere/common/step_wait_for_ip.go +++ b/builder/vsphere/common/step_wait_for_ip.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "log" + "net" "time" "github.com/hashicorp/packer/builder/vsphere/driver" @@ -16,7 +17,7 @@ import ( type WaitIpConfig struct { // Amount of time to wait for VM's IP, similar to 'ssh_timeout'. - // Defaults to 30m (30 minutes). See the Goang + // Defaults to 30m (30 minutes). See the Golang // [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation // for full details. WaitTimeout time.Duration `mapstructure:"ip_wait_timeout"` @@ -27,6 +28,10 @@ type WaitIpConfig struct { // [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation // for full details. SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"` + // Set this to a CIDR address. This will cause the service to wait for an address that is contained in + // this network. Example: "192.168.0.0/24" would look at the address if it was "192.168.0.1". + WaitAddress string `mapstructure:"ip_wait_address"` + ipnet *net.IPNet // WaitTimeout is a total timeout, so even if VM changes IP frequently and it doesn't settle down we will end waiting. } @@ -45,6 +50,14 @@ func (c *WaitIpConfig) Prepare() []error { c.WaitTimeout = 30 * time.Minute } + if c.WaitAddress != "" { + var err error + _, c.ipnet, err = net.ParseCIDR(c.WaitAddress) + if err != nil { + errs = append(errs, fmt.Errorf("unable to parse \"ip_wait_address\": %w", err)) + } + } + return errs } @@ -111,7 +124,7 @@ func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig) (s interval = 1 * time.Second } loop: - ip, err := vm.WaitForIP(ctx) + ip, err := vm.WaitForIP(ctx, c.ipnet) if err != nil { return "", err } diff --git a/builder/vsphere/common/step_wait_for_ip.hcl2spec.go b/builder/vsphere/common/step_wait_for_ip.hcl2spec.go index e8ad9c442..1424a62a4 100644 --- a/builder/vsphere/common/step_wait_for_ip.hcl2spec.go +++ b/builder/vsphere/common/step_wait_for_ip.hcl2spec.go @@ -11,6 +11,7 @@ import ( type FlatWaitIpConfig struct { WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"` SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"` + WaitAddress *string `mapstructure:"ip_wait_address" cty:"ip_wait_address" hcl:"ip_wait_address"` } // FlatMapstructure returns a new FlatWaitIpConfig. @@ -27,6 +28,7 @@ func (*FlatWaitIpConfig) HCL2Spec() map[string]hcldec.Spec { s := map[string]hcldec.Spec{ "ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false}, "ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false}, + "ip_wait_address": &hcldec.AttrSpec{Name: "ip_wait_address", Type: cty.String, Required: false}, } return s } diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index 0296f57b5..72673893f 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "net" "strings" "time" @@ -491,8 +492,39 @@ func (vm *VirtualMachine) PowerOn() error { return err } -func (vm *VirtualMachine) WaitForIP(ctx context.Context) (string, error) { - return vm.vm.WaitForIP(ctx) +func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (string, error) { + var ip string + + p := property.DefaultCollector(vm.vm.Client()) + err := property.Wait(ctx, p, vm.vm.Reference(), []string{"guest.ipAddress"}, func(pc []types.PropertyChange) bool { + for _, c := range pc { + if c.Name != "guest.ipAddress" { + continue + } + if c.Op != types.PropertyChangeOpAssign { + continue + } + if c.Val == nil { + continue + } + + i := c.Val.(string) + if ipNet != nil && ipNet.Contains(net.ParseIP(i)) { + // ip address is not in range + continue + } + ip = i + return true + } + + return false + }) + + if err != nil { + return "", err + } + + return ip, nil } func (vm *VirtualMachine) PowerOff() error { diff --git a/builder/vsphere/driver/vm_clone_acc_test.go b/builder/vsphere/driver/vm_clone_acc_test.go index 664103f1f..062eb1d48 100644 --- a/builder/vsphere/driver/vm_clone_acc_test.go +++ b/builder/vsphere/driver/vm_clone_acc_test.go @@ -235,7 +235,7 @@ func startAndStopCheck(t *testing.T, vm *VirtualMachine, config *CloneConfig) { stopper := startVM(t, vm, config.Name) defer stopper() - switch ip, err := vm.WaitForIP(context.TODO()); { + switch ip, err := vm.WaitForIP(context.TODO(), nil); { case err != nil: t.Errorf("Cannot obtain IP address from created vm '%v': %v", config.Name, err) case net.ParseIP(ip) == nil: diff --git a/builder/vsphere/iso/config.hcl2spec.go b/builder/vsphere/iso/config.hcl2spec.go index d741e285b..c847734ae 100644 --- a/builder/vsphere/iso/config.hcl2spec.go +++ b/builder/vsphere/iso/config.hcl2spec.go @@ -72,6 +72,7 @@ type FlatConfig struct { HTTPIP *string `mapstructure:"http_ip" cty:"http_ip" hcl:"http_ip"` WaitTimeout *string `mapstructure:"ip_wait_timeout" cty:"ip_wait_timeout" hcl:"ip_wait_timeout"` SettleTimeout *string `mapstructure:"ip_settle_timeout" cty:"ip_settle_timeout" hcl:"ip_settle_timeout"` + WaitAddress *string `mapstructure:"ip_wait_address" cty:"ip_wait_address" hcl:"ip_wait_address"` Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` @@ -196,6 +197,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "http_ip": &hcldec.AttrSpec{Name: "http_ip", Type: cty.String, Required: false}, "ip_wait_timeout": &hcldec.AttrSpec{Name: "ip_wait_timeout", Type: cty.String, Required: false}, "ip_settle_timeout": &hcldec.AttrSpec{Name: "ip_settle_timeout", Type: cty.String, Required: false}, + "ip_wait_address": &hcldec.AttrSpec{Name: "ip_wait_address", Type: cty.String, Required: false}, "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, diff --git a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx index 3c6b96739..4ce9daf8e 100644 --- a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx +++ b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx @@ -1,7 +1,7 @@ - `ip_wait_timeout` (duration string | ex: "1h5m2s") - Amount of time to wait for VM's IP, similar to 'ssh_timeout'. - Defaults to 30m (30 minutes). See the Goang + Defaults to 30m (30 minutes). See the Golang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. @@ -11,4 +11,7 @@ 5s(5 seconds). See the Golang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. + +- `ip_wait_address` (string) - Set this to a CIDR address. This will cause the service to wait for an address that is contained in + this network. Example: "192.168.0.0/24" would look at the address if it was "192.168.0.1". \ No newline at end of file From 27a1ceef6d5c664aadc916613bf6674a891cdd65 Mon Sep 17 00:00:00 2001 From: Joshua Foster Date: Thu, 4 Jun 2020 12:15:53 -0400 Subject: [PATCH 2/5] add example for any ipv4 address --- builder/vsphere/common/step_wait_for_ip.go | 2 +- .../builder/vsphere/common/WaitIpConfig-not-required.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/vsphere/common/step_wait_for_ip.go b/builder/vsphere/common/step_wait_for_ip.go index 6c33b887c..8565e389e 100644 --- a/builder/vsphere/common/step_wait_for_ip.go +++ b/builder/vsphere/common/step_wait_for_ip.go @@ -29,7 +29,7 @@ type WaitIpConfig struct { // for full details. SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"` // Set this to a CIDR address. This will cause the service to wait for an address that is contained in - // this network. Example: "192.168.0.0/24" would look at the address if it was "192.168.0.1". + // this network. Use "0.0.0.0/0" to wait for any ipv4 address. WaitAddress string `mapstructure:"ip_wait_address"` ipnet *net.IPNet diff --git a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx index 4ce9daf8e..eff787766 100644 --- a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx +++ b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx @@ -13,5 +13,5 @@ for full details. - `ip_wait_address` (string) - Set this to a CIDR address. This will cause the service to wait for an address that is contained in - this network. Example: "192.168.0.0/24" would look at the address if it was "192.168.0.1". + this network. Use "0.0.0.0/0" to wait for any ipv4 address. \ No newline at end of file From 90463e53940ad1884b96a99a20db4784286fd3c5 Mon Sep 17 00:00:00 2001 From: Joshua Foster Date: Thu, 4 Jun 2020 22:27:59 -0400 Subject: [PATCH 3/5] make default for wait address to be any ipv4 address. add some better documentation --- builder/vsphere/common/step_wait_for_ip.go | 18 +++++++++++++----- .../common/WaitIpConfig-not-required.mdx | 8 ++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/builder/vsphere/common/step_wait_for_ip.go b/builder/vsphere/common/step_wait_for_ip.go index 8565e389e..3d1617f01 100644 --- a/builder/vsphere/common/step_wait_for_ip.go +++ b/builder/vsphere/common/step_wait_for_ip.go @@ -28,9 +28,13 @@ type WaitIpConfig struct { // [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation // for full details. SettleTimeout time.Duration `mapstructure:"ip_settle_timeout"` - // Set this to a CIDR address. This will cause the service to wait for an address that is contained in - // this network. Use "0.0.0.0/0" to wait for any ipv4 address. - WaitAddress string `mapstructure:"ip_wait_address"` + // Set this to a CIDR address to cause the service to wait for an address that is contained in + // this network range. Defaults to "0.0.0.0/0" for any ipv4 address. Examples include: + // + // * empty string ("") - remove all filters + // * "0:0:0:0:0:0:0:0/0" - allow only ipv6 addresses + // * "192.168.1.0/24 - only allow ipv4 addresses from 192.168.1.1 to 192.168.1.254 + WaitAddress *string `mapstructure:"ip_wait_address"` ipnet *net.IPNet // WaitTimeout is a total timeout, so even if VM changes IP frequently and it doesn't settle down we will end waiting. @@ -49,10 +53,14 @@ func (c *WaitIpConfig) Prepare() []error { if c.WaitTimeout == 0 { c.WaitTimeout = 30 * time.Minute } + if c.WaitAddress == nil { + addr := "0.0.0.0/0" + c.WaitAddress = &addr + } - if c.WaitAddress != "" { + if *c.WaitAddress != "" { var err error - _, c.ipnet, err = net.ParseCIDR(c.WaitAddress) + _, c.ipnet, err = net.ParseCIDR(*c.WaitAddress) if err != nil { errs = append(errs, fmt.Errorf("unable to parse \"ip_wait_address\": %w", err)) } diff --git a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx index eff787766..09da727a7 100644 --- a/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx +++ b/website/pages/partials/builder/vsphere/common/WaitIpConfig-not-required.mdx @@ -12,6 +12,10 @@ [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. -- `ip_wait_address` (string) - Set this to a CIDR address. This will cause the service to wait for an address that is contained in - this network. Use "0.0.0.0/0" to wait for any ipv4 address. +- `ip_wait_address` (\*string) - Set this to a CIDR address to cause the service to wait for an address that is contained in + this network range. Defaults to "0.0.0.0/0" for any ipv4 address. Examples include: + + * empty string ("") - remove all filters + * "0:0:0:0:0:0:0:0/0" - allow only ipv6 addresses + * "192.168.1.0/24 - only allow ipv4 addresses from 192.168.1.1 to 192.168.1.254 \ No newline at end of file From 3a6d42e0df534cef9d5f7675cd22cfa9a92d1e1a Mon Sep 17 00:00:00 2001 From: Joshua Foster Date: Thu, 4 Jun 2020 23:17:02 -0400 Subject: [PATCH 4/5] ip check should continue if the ip is NOT in the range --- builder/vsphere/driver/vm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index 72673893f..d42ca64dc 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -509,7 +509,7 @@ func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (stri } i := c.Val.(string) - if ipNet != nil && ipNet.Contains(net.ParseIP(i)) { + if ipNet != nil && !ipNet.Contains(net.ParseIP(i)) { // ip address is not in range continue } From d87e53b8414e14bc482e39d602bd0fd3f81d5d5f Mon Sep 17 00:00:00 2001 From: Joshua Foster Date: Fri, 5 Jun 2020 00:09:57 -0400 Subject: [PATCH 5/5] return false instead of continue to match logic from govmomi --- builder/vsphere/driver/vm.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index d42ca64dc..2170426e9 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -508,12 +508,11 @@ func (vm *VirtualMachine) WaitForIP(ctx context.Context, ipNet *net.IPNet) (stri continue } - i := c.Val.(string) - if ipNet != nil && !ipNet.Contains(net.ParseIP(i)) { + ip = c.Val.(string) + if ipNet != nil && !ipNet.Contains(net.ParseIP(ip)) { // ip address is not in range - continue + return false } - ip = i return true }