diff --git a/packer/provisioner.go b/packer/provisioner.go index 8d8c54a9b..41b79f5a0 100644 --- a/packer/provisioner.go +++ b/packer/provisioner.go @@ -145,17 +145,55 @@ func (p *PausedProvisioner) Prepare(raws ...interface{}) error { func (p *PausedProvisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error { - // Use a select to determine if we get cancelled during the wait ui.Say(fmt.Sprintf("Pausing %s before the next provisioner...", p.PauseBefore)) - select { - case <-time.After(p.PauseBefore): - case <-ctx.Done(): - return ctx.Err() + minimumTimeForPauseUpdate := float64(10) + if p.PauseBefore.Seconds() < minimumTimeForPauseUpdate { + // Use a select to determine if we get cancelled during the wait + select { + case <-time.After(p.PauseBefore): + case <-ctx.Done(): + return ctx.Err() + } + } else { + err := p.updatesWhilePausing(ctx, ui) + if err != nil { + return err + } } return p.Provisioner.Provision(ctx, ui, comm, generatedData) } +func (p *PausedProvisioner) updatesWhilePausing(ctx context.Context, ui packersdk.Ui) error { + updateTime := 10 + timeTicker := time.NewTicker(time.Duration(updateTime) * time.Second) + TotalTime := p.PauseBefore.Seconds() + tickerChannel := make(chan bool) + var err error + go func() { + for { + select { + case <-timeTicker.C: + TotalTime -= float64(updateTime) + ui.Say(fmt.Sprintf("%v seconds left until the next provisioner", TotalTime)) + case <-ctx.Done(): + err = ctx.Err() + return + case <-tickerChannel: + return + } + } + }() + time.Sleep(p.PauseBefore) + timeTicker.Stop() + if err != nil { + return err + } + tickerChannel <- true + + return nil +} + // RetriedProvisioner is a Provisioner implementation that retries // the provisioner whenever there's an error. type RetriedProvisioner struct { diff --git a/packer/provisioner_test.go b/packer/provisioner_test.go index 4fad60b23..70b80d1d3 100644 --- a/packer/provisioner_test.go +++ b/packer/provisioner_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "testing" "time" @@ -129,11 +130,8 @@ func TestPausedProvisionerProvision(t *testing.T) { } } -func TestPausedProvisionerProvision_waits(t *testing.T) { - startTime := time.Now() - waitTime := 50 * time.Millisecond - - prov := &PausedProvisioner{ +func pausedTestProvisionor(startTime time.Time, waitTime time.Duration) *PausedProvisioner { + return &PausedProvisioner{ PauseBefore: waitTime, Provisioner: &packersdk.MockProvisioner{ ProvFunc: func(context.Context) error { @@ -145,7 +143,13 @@ func TestPausedProvisionerProvision_waits(t *testing.T) { }, }, } +} + +func TestPausedProvisionerProvision_waits(t *testing.T) { + startTime := time.Now() + waitTime := 50 * time.Millisecond + prov := pausedTestProvisionor(startTime, waitTime) err := prov.Provision(context.Background(), testUi(), new(packersdk.MockCommunicator), make(map[string]interface{})) if err != nil { @@ -153,6 +157,51 @@ func TestPausedProvisionerProvision_waits(t *testing.T) { } } +func TestPausedProvisionerProvision_waits_with_updates(t *testing.T) { + startTime := time.Now() + waitTime := 30 * time.Second + + prov := pausedTestProvisionor(startTime, waitTime) + ui := new(packersdk.MockUi) + currentTime := time.Now() + err := prov.Provision(context.Background(), ui, new(packersdk.MockCommunicator), make(map[string]interface{})) + + if err != nil { + t.Fatalf("prov failed: %v", err) + } + + // TODO have to put check to get timestamp of when these messages were posted and verify that this is working as intended + expectedMessages := []string{ + fmt.Sprintf("Pausing %s before the next provisioner...", waitTime), + "20 seconds left until the next provisioner", + "10 seconds left until the next provisioner", + } + + if ui.SayMessages[0].Message != expectedMessages[0] { + t.Fatalf("expected: %s, got: %s", expectedMessages[0], ui.SayMessages[0].Message) + } + + lastTime := currentTime + for index, message := range expectedMessages { + // Skiping first message as this has already been verified + if index == 0 { + continue + } + + if ui.SayMessages[index].Message != message { + t.Fatalf("expected: %s, got: %s", message, ui.SayMessages[index].Message) + } + + waitTimeBetweenMessages := math.Round(ui.SayMessages[index].SayTime.Sub(lastTime).Seconds()) + if waitTimeBetweenMessages != 10 { + t.Fatalf("Did not wait the appropriate amount of time message: %v", ui.SayMessages[index].Message) + } + + // setting last time to current SayTime + lastTime = ui.SayMessages[index].SayTime + } +} + func TestPausedProvisionerCancel(t *testing.T) { topCtx, cancelTopCtx := context.WithCancel(context.Background())