diff --git a/command/hook_ui.go b/command/hook_ui.go index 60372123d..df5b6c09a 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -33,8 +33,12 @@ type UiHook struct { // uiResourceState tracks the state of a single resource type uiResourceState struct { - Op uiResourceOp - Start time.Time + Name string + ResourceId string + Op uiResourceOp + Start time.Time + + DoneCh chan struct{} // To be used for cancellation } // uiResourceOp is an enum for operations on a resource @@ -63,13 +67,6 @@ func (h *UiHook) PreApply( op = uiResourceCreate } - h.l.Lock() - h.resources[id] = uiResourceState{ - Op: op, - Start: time.Now().Round(time.Second), - } - h.l.Unlock() - var operation string switch op { case uiResourceModify: @@ -131,8 +128,9 @@ func (h *UiHook) PreApply( attrString = "\n " + attrString } - var stateIdSuffix string + var stateId, stateIdSuffix string if s != nil && s.ID != "" { + stateId = s.ID stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen)) } @@ -143,51 +141,59 @@ func (h *UiHook) PreApply( stateIdSuffix, attrString))) - // Set a timer to show an operation is still happening - time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id, s) }) + uiState := uiResourceState{ + Name: id, + ResourceId: stateId, + Op: op, + Start: time.Now().Round(time.Second), + DoneCh: make(chan struct{}), + } + + h.l.Lock() + h.resources[id] = uiState + h.l.Unlock() + + // Start goroutine that shows progress + go h.stillApplying(uiState) return terraform.HookActionContinue, nil } -func (h *UiHook) stillApplying(id string, s *terraform.InstanceState) { - // Grab the operation. We defer the lock here to avoid the "still..." - // message showing up after a completion message. - h.l.Lock() - defer h.l.Unlock() - state, ok := h.resources[id] +func (h *UiHook) stillApplying(state uiResourceState) { + for { + select { + case <-state.DoneCh: + return - // If the resource is out of the map it means we're done with it - if !ok { - return + case <-time.After(periodicUiTimer): + // Timer up, show status + } + + var msg string + switch state.Op { + case uiResourceModify: + msg = "Still modifying..." + case uiResourceDestroy: + msg = "Still destroying..." + case uiResourceCreate: + msg = "Still creating..." + case uiResourceUnknown: + return + } + + idSuffix := "" + if v := state.ResourceId; v != "" { + idSuffix = fmt.Sprintf("ID: %s, ", truncateId(v, maxIdLen)) + } + + h.ui.Output(h.Colorize.Color(fmt.Sprintf( + "[reset][bold]%s: %s (%s%s elapsed)[reset]", + state.Name, + msg, + idSuffix, + time.Now().Round(time.Second).Sub(state.Start), + ))) } - - var msg string - switch state.Op { - case uiResourceModify: - msg = "Still modifying..." - case uiResourceDestroy: - msg = "Still destroying..." - case uiResourceCreate: - msg = "Still creating..." - case uiResourceUnknown: - return - } - - var stateIdSuffix string - if s != nil && s.ID != "" { - stateIdSuffix = fmt.Sprintf("ID: %s, ", truncateId(s.ID, maxIdLen)) - } - - h.ui.Output(h.Colorize.Color(fmt.Sprintf( - "[reset][bold]%s: %s (%s%s elapsed)[reset]", - id, - msg, - stateIdSuffix, - time.Now().Round(time.Second).Sub(state.Start), - ))) - - // Reschedule - time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id, s) }) } func (h *UiHook) PostApply( @@ -200,6 +206,10 @@ func (h *UiHook) PostApply( h.l.Lock() state := h.resources[id] + if state.DoneCh != nil { + close(state.DoneCh) + } + delete(h.resources, id) h.l.Unlock()