command: Display state ID in PreApply+PostApply (#12261)

This commit is contained in:
Radek Simko 2017-03-01 22:16:22 +00:00 committed by GitHub
parent 185c3dffe0
commit 2e2b8686dd
2 changed files with 125 additions and 8 deletions

View File

@ -16,6 +16,7 @@ import (
) )
const periodicUiTimer = 10 * time.Second const periodicUiTimer = 10 * time.Second
const maxIdLen = 20
type UiHook struct { type UiHook struct {
terraform.NilHook terraform.NilHook
@ -128,19 +129,25 @@ func (h *UiHook) PreApply(
attrString = "\n " + attrString attrString = "\n " + attrString
} }
var stateIdSuffix string
if s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s[reset]%s", "[reset][bold]%s: %s%s[reset]%s",
id, id,
operation, operation,
stateIdSuffix,
attrString))) attrString)))
// Set a timer to show an operation is still happening // Set a timer to show an operation is still happening
time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id) }) time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id, s.ID) })
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
func (h *UiHook) stillApplying(id string) { func (h *UiHook) stillApplying(id, stateId string) {
// Grab the operation. We defer the lock here to avoid the "still..." // Grab the operation. We defer the lock here to avoid the "still..."
// message showing up after a completion message. // message showing up after a completion message.
h.l.Lock() h.l.Lock()
@ -164,15 +171,21 @@ func (h *UiHook) stillApplying(id string) {
return return
} }
var stateIdSuffix string
if stateId != "" {
stateIdSuffix = fmt.Sprintf("ID: %s, ", truncateId(stateId, maxIdLen))
}
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s (%s elapsed)[reset]", "[reset][bold]%s: %s (%s%s elapsed)[reset]",
id, id,
msg, msg,
stateIdSuffix,
time.Now().Round(time.Second).Sub(state.Start), time.Now().Round(time.Second).Sub(state.Start),
))) )))
// Reschedule // Reschedule
time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id) }) time.AfterFunc(periodicUiTimer, func() { h.stillApplying(id, stateId) })
} }
func (h *UiHook) PostApply( func (h *UiHook) PostApply(
@ -186,6 +199,11 @@ func (h *UiHook) PostApply(
delete(h.resources, id) delete(h.resources, id)
h.l.Unlock() h.l.Unlock()
var stateIdSuffix string
if s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
}
var msg string var msg string
switch state.Op { switch state.Op {
case uiResourceModify: case uiResourceModify:
@ -204,8 +222,8 @@ func (h *UiHook) PostApply(
} }
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: %s[reset]", "[reset][bold]%s: %s%s[reset]",
id, msg))) id, msg, stateIdSuffix)))
return terraform.HookActionContinue, nil return terraform.HookActionContinue, nil
} }
@ -258,7 +276,7 @@ func (h *UiHook) PreRefresh(
// Data resources refresh before they have ids, whereas managed // Data resources refresh before they have ids, whereas managed
// resources are only refreshed when they have ids. // resources are only refreshed when they have ids.
if s.ID != "" { if s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", s.ID) stateIdSuffix = fmt.Sprintf(" (ID: %s)", truncateId(s.ID, maxIdLen))
} }
h.ui.Output(h.Colorize.Color(fmt.Sprintf( h.ui.Output(h.Colorize.Color(fmt.Sprintf(
@ -336,3 +354,32 @@ func dropCR(data []byte) []byte {
} }
return data return data
} }
func truncateId(id string, maxLen int) string {
totalLength := len(id)
if totalLength <= maxLen {
return id
}
if maxLen < 5 {
// We don't shorten to less than 5 chars
// as that would be pointless with ... (3 chars)
maxLen = 5
}
dots := "..."
partLen := maxLen / 2
leftIdx := partLen - 1
leftPart := id[0:leftIdx]
rightIdx := totalLength - partLen - 1
overlap := maxLen - (partLen*2 + len(dots))
if overlap < 0 {
rightIdx -= overlap
}
rightPart := id[rightIdx:]
return leftPart + dots + rightPart
}

70
command/hook_ui_test.go Normal file
View File

@ -0,0 +1,70 @@
package command
import (
"fmt"
"testing"
)
func TestTruncateId(t *testing.T) {
testCases := []struct {
Input string
Expected string
MaxLen int
}{
{
Input: "Hello world",
Expected: "H...d",
MaxLen: 3,
},
{
Input: "Hello world",
Expected: "H...d",
MaxLen: 5,
},
{
Input: "Hello world",
Expected: "He...d",
MaxLen: 6,
},
{
Input: "Hello world",
Expected: "He...ld",
MaxLen: 7,
},
{
Input: "Hello world",
Expected: "Hel...ld",
MaxLen: 8,
},
{
Input: "Hello world",
Expected: "Hel...rld",
MaxLen: 9,
},
{
Input: "Hello world",
Expected: "Hell...rld",
MaxLen: 10,
},
{
Input: "Hello world",
Expected: "Hello world",
MaxLen: 11,
},
{
Input: "Hello world",
Expected: "Hello world",
MaxLen: 12,
},
}
for i, tc := range testCases {
testName := fmt.Sprintf("%d", i)
t.Run(testName, func(t *testing.T) {
out := truncateId(tc.Input, tc.MaxLen)
if out != tc.Expected {
t.Fatalf("Expected %q to be shortened to %d as %q (given: %q)",
tc.Input, tc.MaxLen, tc.Expected, out)
}
})
}
}