diff --git a/README.md b/README.md index 024b91b9b..196cb5aa0 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ See complete Ubuntu, Windows, and macOS templates in the [examples folder](https ### Provision * `communicator` - `ssh` (default), `winrm`, or `none` (create/clone, customize hardware, but do not boot). +* `ip_settle_timeout`(string) - Amount of time to wait for VM's IP to settle down, sometimes VM may report incorrect IP initially, then its recommended to set that parameter to apx. 2 minutes. Examples 45s and 10m. Defaults to 5s(5 seconds). See the Go Lang [ParseDuration](https://golang.org/pkg/time/#ParseDuration) documentation for full details. * `ssh_username`(string) - Username in guest OS. * `ssh_password`(string) - Password to access guest OS. Only specify `ssh_password` or `ssh_private_key_file`, but not both. * `ssh_private_key_file`(string) - Path to the SSH private key file to access guest OS. Only specify `ssh_password` or `ssh_private_key_file`, but not both. diff --git a/clone/builder.go b/clone/builder.go index ec3356709..f7ba7b77c 100644 --- a/clone/builder.go +++ b/clone/builder.go @@ -55,7 +55,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Config: &b.config.RunConfig, SetOrder: false, }, - &common.StepWaitForIp{}, + &common.StepWaitForIp{ + &b.config.WaitIpConfig, + }, &communicator.StepConnect{ Config: &b.config.Comm, Host: common.CommHost(b.config.Comm.SSHHost), diff --git a/clone/config.go b/clone/config.go index 420d0d495..bcb8b791b 100644 --- a/clone/config.go +++ b/clone/config.go @@ -19,6 +19,7 @@ type Config struct { common.ConfigParamsConfig `mapstructure:",squash"` common.RunConfig `mapstructure:",squash"` + common.WaitIpConfig `mapstructure:",squash"` Comm communicator.Config `mapstructure:",squash"` common.ShutdownConfig `mapstructure:",squash"` @@ -44,6 +45,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.LocationConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.HardwareConfig.Prepare()...) + errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...) diff --git a/common/step_wait_for_ip.go b/common/step_wait_for_ip.go index 3cc1cfae4..e81460df5 100644 --- a/common/step_wait_for_ip.go +++ b/common/step_wait_for_ip.go @@ -6,10 +6,35 @@ import ( "github.com/hashicorp/packer/helper/multistep" "github.com/hashicorp/packer/packer" "github.com/jetbrains-infra/packer-builder-vsphere/driver" + "log" "time" ) -type StepWaitForIp struct{} +type WaitIpConfig struct { + SettleTimeout string `mapstructure:"ip_settle_timeout"` + + settleTimeout time.Duration +} + +type StepWaitForIp struct { + Config *WaitIpConfig +} + +func (c *WaitIpConfig) Prepare() []error { + var errs []error + + if c.SettleTimeout == "" { + c.SettleTimeout = "5s" + } + + var err error + c.settleTimeout, err = time.ParseDuration(c.SettleTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("failed parsing ip_settle_timeout: %s", err)) + } + + return errs +} func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) @@ -20,12 +45,7 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi ipChan := make(chan string) errChan := make(chan error) go func() { - ip, err := vm.WaitForIP(ctx) - if err != nil { - errChan <- err - } else { - ipChan <- ip - } + doGetIp(vm, ctx, s.Config, errChan, ipChan) }() for { @@ -47,4 +67,49 @@ func (s *StepWaitForIp) Run(ctx context.Context, state multistep.StateBag) multi } } +func doGetIp(vm *driver.VirtualMachine, ctx context.Context, c *WaitIpConfig, errChan chan error, ipChan chan string) { + var prevIp = "" + var stopTime time.Time + var interval time.Duration + if c.settleTimeout.Seconds() >= 120 { + interval = 30 * time.Second + } else if c.settleTimeout.Seconds() >= 60 { + interval = 15 * time.Second + } else if c.settleTimeout.Seconds() >= 10 { + interval = 5 * time.Second + } else { + interval = 1 * time.Second + } +loop: + ip, err := vm.WaitForIP(ctx) + if err != nil { + errChan <- err + return + } + if prevIp == "" || prevIp != ip { + if prevIp == "" { + log.Printf("VM IP aquired: %s", ip) + } else { + log.Printf("VM IP changed from %s to %s", prevIp, ip) + } + prevIp = ip + stopTime = time.Now().Add(c.settleTimeout) + goto loop + } else { + log.Printf("VM IP is still the same: %s", prevIp) + if time.Now().After(stopTime) { + log.Printf("VM IP seems stable enough: %s", ip) + ipChan <- ip + return + } + select { + case <-ctx.Done(): + return + case <-time.After(interval): + goto loop + } + } + +} + func (s *StepWaitForIp) Cleanup(state multistep.StateBag) {} diff --git a/iso/builder.go b/iso/builder.go index 91fc8d630..b8345e86b 100644 --- a/iso/builder.go +++ b/iso/builder.go @@ -99,7 +99,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Ctx: b.config.ctx, VMName: b.config.VMName, }, - &common.StepWaitForIp{}, + &common.StepWaitForIp{ + Config: &b.config.WaitIpConfig, + }, &communicator.StepConnect{ Config: &b.config.Comm, Host: common.CommHost(b.config.Comm.SSHHost), diff --git a/iso/config.go b/iso/config.go index 5e032126f..a6ba2953b 100644 --- a/iso/config.go +++ b/iso/config.go @@ -21,11 +21,12 @@ type Config struct { packerCommon.ISOConfig `mapstructure:",squash"` - CDRomConfig `mapstructure:",squash"` - FloppyConfig `mapstructure:",squash"` - common.RunConfig `mapstructure:",squash"` - BootConfig `mapstructure:",squash"` - Comm communicator.Config `mapstructure:",squash"` + CDRomConfig `mapstructure:",squash"` + FloppyConfig `mapstructure:",squash"` + common.RunConfig `mapstructure:",squash"` + BootConfig `mapstructure:",squash"` + common.WaitIpConfig `mapstructure:",squash"` + Comm communicator.Config `mapstructure:",squash"` common.ShutdownConfig `mapstructure:",squash"` @@ -67,6 +68,7 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) { errs = packer.MultiErrorAppend(errs, c.CDRomConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.BootConfig.Prepare()...) + errs = packer.MultiErrorAppend(errs, c.WaitIpConfig.Prepare()...) errs = packer.MultiErrorAppend(errs, c.Comm.Prepare(&c.ctx)...) errs = packer.MultiErrorAppend(errs, c.ShutdownConfig.Prepare()...)