diff --git a/command/hook_ui.go b/command/hook_ui.go index 6a10ceefb..e1a1cb9a9 100644 --- a/command/hook_ui.go +++ b/command/hook_ui.go @@ -7,6 +7,7 @@ import ( "sort" "strings" "sync" + "time" "unicode" "github.com/hashicorp/terraform/terraform" @@ -14,6 +15,8 @@ import ( "github.com/mitchellh/colorstring" ) +const periodicUiTimer = 10 * time.Second + type UiHook struct { terraform.NilHook @@ -22,10 +25,17 @@ type UiHook struct { l sync.Mutex once sync.Once - resources map[string]uiResourceOp + resources map[string]uiResourceState ui cli.Ui } +// uiResourceState tracks the state of a single resource +type uiResourceState struct { + Op uiResourceOp + Start time.Time +} + +// uiResourceOp is an enum for operations on a resource type uiResourceOp byte const ( @@ -51,7 +61,10 @@ func (h *UiHook) PreApply( } h.l.Lock() - h.resources[id] = op + h.resources[id] = uiResourceState{ + Op: op, + Start: time.Now().Round(time.Second), + } h.l.Unlock() var operation string @@ -113,9 +126,46 @@ func (h *UiHook) PreApply( operation, attrString))) + // Set a timer to show an operation is still happening + time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id) }) + return terraform.HookActionContinue, nil } +func (h *UiHook) stillApplying(id string) { + // Grab the operation + h.l.Lock() + state, ok := h.resources[id] + h.l.Unlock() + + // If the resource is out of the map it means we're done with it + if !ok { + return + } + + var msg string + switch state.Op { + case uiResourceModify: + msg = "Still modifying..." + case uiResourceDestroy: + msg = "Still destroying..." + case uiResourceCreate: + msg = "Still creating..." + case uiResourceUnknown: + return + } + + h.ui.Output(h.Colorize.Color(fmt.Sprintf( + "[reset][bold]%s: %s (%s elapsed)[reset_bold]", + id, + msg, + time.Now().Round(time.Second).Sub(state.Start), + ))) + + // Reschedule + time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id) }) +} + func (h *UiHook) PostApply( n *terraform.InstanceInfo, s *terraform.InstanceState, @@ -123,12 +173,12 @@ func (h *UiHook) PostApply( id := n.HumanId() h.l.Lock() - op := h.resources[id] + state := h.resources[id] delete(h.resources, id) h.l.Unlock() var msg string - switch op { + switch state.Op { case uiResourceModify: msg = "Modifications complete" case uiResourceDestroy: @@ -205,7 +255,7 @@ func (h *UiHook) init() { panic("colorize not given") } - h.resources = make(map[string]uiResourceOp) + h.resources = make(map[string]uiResourceState) // Wrap the ui so that it is safe for concurrency regardless of the // underlying reader/writer that is in place.