Properly display multi-step replaces in the progress UI. (#1679)
This commit is contained in:
parent
c4f08db78b
commit
3d063e200c
|
@ -44,9 +44,6 @@ func DisplayEvents(
|
||||||
if opts.DiffDisplay {
|
if opts.DiffDisplay {
|
||||||
DisplayDiffEvents(action, events, done, opts)
|
DisplayDiffEvents(action, events, done, opts)
|
||||||
} else {
|
} else {
|
||||||
// in progress display, we can't show separate create/delete for a single resource.
|
|
||||||
// we have to always show them as a single 'replace'.
|
|
||||||
opts.ShowReplacementSteps = false
|
|
||||||
DisplayProgressEvents(action, events, done, opts)
|
DisplayProgressEvents(action, events, done, opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,11 +311,8 @@ func shouldShow(step engine.StepEventMetadata, opts backend.DisplayOptions) bool
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return opts.ShowSameResources
|
return opts.ShowSameResources
|
||||||
} else if step.Op == deploy.OpCreateReplacement || step.Op == deploy.OpDeleteReplaced {
|
|
||||||
return opts.ShowReplacementSteps
|
|
||||||
} else if step.Op == deploy.OpReplace {
|
|
||||||
return !opts.ShowReplacementSteps
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ type ProgressDisplay struct {
|
||||||
terminalWidth int
|
terminalWidth int
|
||||||
|
|
||||||
// If all progress messages are done and we can print out the final display.
|
// If all progress messages are done and we can print out the final display.
|
||||||
Done bool
|
done bool
|
||||||
|
|
||||||
// The column that the suffix should be added to
|
// The column that the suffix should be added to
|
||||||
suffixColumn int
|
suffixColumn int
|
||||||
|
@ -627,21 +627,24 @@ func (display *ProgressDisplay) refreshAllRowsIfInTerminal() {
|
||||||
// Specifically, this will update the status messages for any resources, and will also then
|
// Specifically, this will update the status messages for any resources, and will also then
|
||||||
// print out all final diagnostics. and finally will print out the summary.
|
// print out all final diagnostics. and finally will print out the summary.
|
||||||
func (display *ProgressDisplay) processEndSteps() {
|
func (display *ProgressDisplay) processEndSteps() {
|
||||||
display.Done = true
|
// Figure out the rows that are currently in progress.
|
||||||
|
inProgressRows := []ResourceRow{}
|
||||||
|
|
||||||
for _, v := range display.eventUrnToResourceRow {
|
for _, v := range display.eventUrnToResourceRow {
|
||||||
// transition everything to the done state. If we're not in an a terminal and this is a
|
if !v.IsDone() {
|
||||||
// transition, then print out the transition. Don't bother doing this in a terminal as
|
inProgressRows = append(inProgressRows, v)
|
||||||
// we're going to refresh everything when we break out of the loop.
|
}
|
||||||
if !v.Done() {
|
}
|
||||||
v.SetDone()
|
|
||||||
|
|
||||||
if !display.isTerminal {
|
// transition the display to the 'done' state. this will transitively cause all
|
||||||
display.refreshSingleRow("", v, nil)
|
// rows to become done.
|
||||||
}
|
display.done = true
|
||||||
} else {
|
|
||||||
// Explicitly transition the status so that we clear out any cached data for it.
|
// Now print out all those rows that were in progress. They will now be 'done'
|
||||||
v.SetDone()
|
// since the display was marked 'done'.
|
||||||
|
if !display.isTerminal {
|
||||||
|
for _, v := range inProgressRows {
|
||||||
|
display.refreshSingleRow("", v, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -871,19 +874,11 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
||||||
|
|
||||||
if event.Type == engine.ResourcePreEvent {
|
if event.Type == engine.ResourcePreEvent {
|
||||||
step := event.Payload.(engine.ResourcePreEventPayload).Metadata
|
step := event.Payload.(engine.ResourcePreEventPayload).Metadata
|
||||||
if step.Op == "" {
|
|
||||||
contract.Failf("Got empty op for %s", event.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
row.SetStep(step)
|
row.SetStep(step)
|
||||||
} else if event.Type == engine.ResourceOutputsEvent {
|
} else if event.Type == engine.ResourceOutputsEvent {
|
||||||
// transition the status to done.
|
|
||||||
if !isRootEvent {
|
|
||||||
row.SetDone()
|
|
||||||
}
|
|
||||||
|
|
||||||
step := event.Payload.(engine.ResourceOutputsEventPayload).Metadata
|
step := event.Payload.(engine.ResourceOutputsEventPayload).Metadata
|
||||||
row.SetStep(step)
|
row.SetStep(step)
|
||||||
|
row.AddOutputStep(step)
|
||||||
|
|
||||||
// If we're not in a terminal, we may not want to display this row again: if we're displaying a preview or if
|
// If we're not in a terminal, we may not want to display this row again: if we're displaying a preview or if
|
||||||
// this step is a no-op for a custom resource, refreshing this row will simply duplicate its earlier output.
|
// this step is a no-op for a custom resource, refreshing this row will simply duplicate its earlier output.
|
||||||
|
@ -892,7 +887,6 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if event.Type == engine.ResourceOperationFailed {
|
} else if event.Type == engine.ResourceOperationFailed {
|
||||||
row.SetDone()
|
|
||||||
row.SetFailed()
|
row.SetFailed()
|
||||||
} else if event.Type == engine.DiagEvent {
|
} else if event.Type == engine.DiagEvent {
|
||||||
// also record this diagnostic so we print it at the end.
|
// also record this diagnostic so we print it at the end.
|
||||||
|
@ -956,7 +950,6 @@ func (display *ProgressDisplay) processEvents(ticker *time.Ticker, events <-chan
|
||||||
// Main processing loop. The purpose of this func is to read in events from the engine
|
// Main processing loop. The purpose of this func is to read in events from the engine
|
||||||
// and translate them into Status objects and progress messages to be presented to the
|
// and translate them into Status objects and progress messages to be presented to the
|
||||||
// command line.
|
// command line.
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
|
@ -994,19 +987,19 @@ func (display *ProgressDisplay) getStepDoneDescription(step engine.StepEventMeta
|
||||||
return colors.SpecError + "**" + v + "**" + colors.Reset
|
return colors.SpecError + "**" + v + "**" + colors.Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op := display.getStepOp(step)
|
||||||
|
|
||||||
if display.isPreview {
|
if display.isPreview {
|
||||||
// During a preview, when we transition to done, we still just print the same thing we
|
// During a preview, when we transition to done, we still just print the same thing we
|
||||||
// did while running the step.
|
// did while running the step.
|
||||||
return step.Op.Color() + getPreviewText(step.Op) + colors.Reset
|
return op.Color() + display.getPreviewText(op) + colors.Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
// most of the time a stack is unchanged. in that case we just show it as "running->done"
|
// most of the time a stack is unchanged. in that case we just show it as "running->done"
|
||||||
if isRootStack(step) && step.Op == deploy.OpSame {
|
if isRootStack(step) && op == deploy.OpSame {
|
||||||
return "done"
|
return "done"
|
||||||
}
|
}
|
||||||
|
|
||||||
op := step.Op
|
|
||||||
|
|
||||||
getDescription := func() string {
|
getDescription := func() string {
|
||||||
if failed {
|
if failed {
|
||||||
switch op {
|
switch op {
|
||||||
|
@ -1034,9 +1027,9 @@ func (display *ProgressDisplay) getStepDoneDescription(step engine.StepEventMeta
|
||||||
case deploy.OpReplace:
|
case deploy.OpReplace:
|
||||||
return "replaced"
|
return "replaced"
|
||||||
case deploy.OpCreateReplacement:
|
case deploy.OpCreateReplacement:
|
||||||
return "created for replacement"
|
return "created replacement"
|
||||||
case deploy.OpDeleteReplaced:
|
case deploy.OpDeleteReplaced:
|
||||||
return "deleted for replacement"
|
return "deleted original"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1051,7 +1044,7 @@ func (display *ProgressDisplay) getStepDoneDescription(step engine.StepEventMeta
|
||||||
return op.Color() + getDescription() + colors.Reset
|
return op.Color() + getDescription() + colors.Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPreviewText(op deploy.StepOp) string {
|
func (display *ProgressDisplay) getPreviewText(op deploy.StepOp) string {
|
||||||
switch op {
|
switch op {
|
||||||
case deploy.OpSame:
|
case deploy.OpSame:
|
||||||
return "no change"
|
return "no change"
|
||||||
|
@ -1064,21 +1057,46 @@ func getPreviewText(op deploy.StepOp) string {
|
||||||
case deploy.OpReplace:
|
case deploy.OpReplace:
|
||||||
return "replace"
|
return "replace"
|
||||||
case deploy.OpCreateReplacement:
|
case deploy.OpCreateReplacement:
|
||||||
return "create for replacement"
|
return "create replacement"
|
||||||
case deploy.OpDeleteReplaced:
|
case deploy.OpDeleteReplaced:
|
||||||
return "delete for replacement"
|
return "delete original"
|
||||||
}
|
}
|
||||||
|
|
||||||
contract.Failf("Unrecognized resource step op: %v", op)
|
contract.Failf("Unrecognized resource step op: %v", op)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (display *ProgressDisplay) getStepOp(step engine.StepEventMetadata) deploy.StepOp {
|
||||||
|
op := step.Op
|
||||||
|
|
||||||
|
// We will commonly hear about replacements as an actual series of steps. i.e. 'create
|
||||||
|
// replacement', 'replace', 'delete original'. During the actual application of these steps we
|
||||||
|
// want to see these individual steps. However, both before we apply all of them, and after
|
||||||
|
// they're all done, we want to show this as a single conceptual 'replace'/'replaced' step.
|
||||||
|
//
|
||||||
|
// Note: in non-interactive mode we can show these all as individual steps. This only applies
|
||||||
|
// to interactive mode, where there is only one line shown per resource, and we want it to be as
|
||||||
|
// clear as possible
|
||||||
|
if display.isTerminal {
|
||||||
|
// During preview, show the steps for replacing as a single 'replace' plan.
|
||||||
|
// Once done, show the steps for replacing as a single 'replaced' step.
|
||||||
|
// During update, we'll show these individual steps.
|
||||||
|
if display.isPreview || display.done {
|
||||||
|
if op == deploy.OpCreateReplacement || op == deploy.OpDeleteReplaced {
|
||||||
|
return deploy.OpReplace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
|
||||||
func (display *ProgressDisplay) getStepOpLabel(step engine.StepEventMetadata) string {
|
func (display *ProgressDisplay) getStepOpLabel(step engine.StepEventMetadata) string {
|
||||||
return step.Op.Prefix() + colors.Reset
|
return display.getStepOp(step).Prefix() + colors.Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
func (display *ProgressDisplay) getStepInProgressDescription(step engine.StepEventMetadata) string {
|
func (display *ProgressDisplay) getStepInProgressDescription(step engine.StepEventMetadata) string {
|
||||||
op := step.Op
|
op := display.getStepOp(step)
|
||||||
|
|
||||||
if isRootStack(step) && op == deploy.OpSame {
|
if isRootStack(step) && op == deploy.OpSame {
|
||||||
// most of the time a stack is unchanged. in that case we just show it as "running->done".
|
// most of the time a stack is unchanged. in that case we just show it as "running->done".
|
||||||
|
@ -1088,7 +1106,7 @@ func (display *ProgressDisplay) getStepInProgressDescription(step engine.StepEve
|
||||||
|
|
||||||
getDescription := func() string {
|
getDescription := func() string {
|
||||||
if display.isPreview {
|
if display.isPreview {
|
||||||
return getPreviewText(op)
|
return display.getPreviewText(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch op {
|
switch op {
|
||||||
|
@ -1103,9 +1121,9 @@ func (display *ProgressDisplay) getStepInProgressDescription(step engine.StepEve
|
||||||
case deploy.OpReplace:
|
case deploy.OpReplace:
|
||||||
return "replacing"
|
return "replacing"
|
||||||
case deploy.OpCreateReplacement:
|
case deploy.OpCreateReplacement:
|
||||||
return "creating for replacement"
|
return "creating replacement"
|
||||||
case deploy.OpDeleteReplaced:
|
case deploy.OpDeleteReplaced:
|
||||||
return "deleting for replacement"
|
return "deleting original"
|
||||||
}
|
}
|
||||||
|
|
||||||
contract.Failf("Unrecognized resource step op: %v", op)
|
contract.Failf("Unrecognized resource step op: %v", op)
|
||||||
|
|
|
@ -42,13 +42,13 @@ type ResourceRow interface {
|
||||||
|
|
||||||
Step() engine.StepEventMetadata
|
Step() engine.StepEventMetadata
|
||||||
SetStep(step engine.StepEventMetadata)
|
SetStep(step engine.StepEventMetadata)
|
||||||
|
AddOutputStep(step engine.StepEventMetadata)
|
||||||
|
|
||||||
// The tick we were on when we created this row. Purely used for generating an
|
// The tick we were on when we created this row. Purely used for generating an
|
||||||
// ellipses to show progress for in-flight resources.
|
// ellipses to show progress for in-flight resources.
|
||||||
Tick() int
|
Tick() int
|
||||||
|
|
||||||
Done() bool
|
IsDone() bool
|
||||||
SetDone()
|
|
||||||
|
|
||||||
SetFailed()
|
SetFailed()
|
||||||
|
|
||||||
|
@ -111,15 +111,13 @@ type resourceRowData struct {
|
||||||
display *ProgressDisplay
|
display *ProgressDisplay
|
||||||
|
|
||||||
// The change that the engine wants apply to that resource.
|
// The change that the engine wants apply to that resource.
|
||||||
step engine.StepEventMetadata
|
step engine.StepEventMetadata
|
||||||
|
outputSteps []engine.StepEventMetadata
|
||||||
|
|
||||||
// The tick we were on when we created this row. Purely used for generating an
|
// The tick we were on when we created this row. Purely used for generating an
|
||||||
// ellipses to show progress for in-flight resources.
|
// ellipses to show progress for in-flight resources.
|
||||||
tick int
|
tick int
|
||||||
|
|
||||||
// If the engine finished processing this resources.
|
|
||||||
done bool
|
|
||||||
|
|
||||||
// If we failed this operation for any reason.
|
// If we failed this operation for any reason.
|
||||||
failed bool
|
failed bool
|
||||||
|
|
||||||
|
@ -155,29 +153,17 @@ func (data *resourceRowData) Step() engine.StepEventMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *resourceRowData) SetStep(step engine.StepEventMetadata) {
|
func (data *resourceRowData) SetStep(step engine.StepEventMetadata) {
|
||||||
// never update a 'replace' step with an CreateReplacement DeleteReplacement step.
|
|
||||||
// in the progress view we never want to show those individually, we always want
|
|
||||||
// them combined since we only show a single line per resource.
|
|
||||||
if data.step.Op == deploy.OpReplace &&
|
|
||||||
(step.Op == deploy.OpCreateReplacement || step.Op == deploy.OpDeleteReplaced) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data.step = step
|
data.step = step
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (data *resourceRowData) AddOutputStep(step engine.StepEventMetadata) {
|
||||||
|
data.outputSteps = append(data.outputSteps, step)
|
||||||
|
}
|
||||||
|
|
||||||
func (data *resourceRowData) Tick() int {
|
func (data *resourceRowData) Tick() int {
|
||||||
return data.tick
|
return data.tick
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *resourceRowData) Done() bool {
|
|
||||||
return data.done
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *resourceRowData) SetDone() {
|
|
||||||
data.done = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (data *resourceRowData) Failed() bool {
|
func (data *resourceRowData) Failed() bool {
|
||||||
return data.failed
|
return data.failed
|
||||||
}
|
}
|
||||||
|
@ -248,13 +234,39 @@ const (
|
||||||
infoColumn column = 4
|
infoColumn column = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (data *resourceRowData) IsDone() bool {
|
||||||
|
if data.failed {
|
||||||
|
// consider a failed resource 'done'.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.display.done {
|
||||||
|
// if the display is done, then we're definitely done.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're done if we have the output-step for whatever step operation we're performing
|
||||||
|
return data.ContainsOutputsStep(data.step.Op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *resourceRowData) ContainsOutputsStep(op deploy.StepOp) bool {
|
||||||
|
for _, s := range data.outputSteps {
|
||||||
|
if s.Op == op {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (data *resourceRowData) ColorizedSuffix() string {
|
func (data *resourceRowData) ColorizedSuffix() string {
|
||||||
if !data.display.Done && !data.done {
|
if !data.IsDone() && data.display.isTerminal {
|
||||||
if data.step.Op != deploy.OpSame || isRootURN(data.step.URN) {
|
op := data.display.getStepOp(data.step)
|
||||||
|
if op != deploy.OpSame || isRootURN(data.step.URN) {
|
||||||
suffixes := data.display.suffixesArray
|
suffixes := data.display.suffixesArray
|
||||||
ellipses := suffixes[(data.tick+data.display.currentTick)%len(suffixes)]
|
ellipses := suffixes[(data.tick+data.display.currentTick)%len(suffixes)]
|
||||||
|
|
||||||
return data.step.Op.Color() + ellipses + colors.Reset
|
return op.Color() + ellipses + colors.Reset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +293,7 @@ func (data *resourceRowData) ColorizedColumns() []string {
|
||||||
|
|
||||||
diagInfo := data.diagInfo
|
diagInfo := data.diagInfo
|
||||||
|
|
||||||
if data.done {
|
if data.IsDone() {
|
||||||
failed := data.failed || diagInfo.ErrorCount > 0
|
failed := data.failed || diagInfo.ErrorCount > 0
|
||||||
columns[statusColumn] = data.display.getStepDoneDescription(step, failed)
|
columns[statusColumn] = data.display.getStepDoneDescription(step, failed)
|
||||||
} else {
|
} else {
|
||||||
|
@ -294,6 +306,17 @@ func (data *resourceRowData) ColorizedColumns() []string {
|
||||||
|
|
||||||
func (data *resourceRowData) getInfoColumn() string {
|
func (data *resourceRowData) getInfoColumn() string {
|
||||||
step := data.step
|
step := data.step
|
||||||
|
|
||||||
|
if step.Op == deploy.OpCreateReplacement || step.Op == deploy.OpDeleteReplaced {
|
||||||
|
// if we're doing a replacement, see if we can find a replace step that contains useful
|
||||||
|
// information to display.
|
||||||
|
for _, outputStep := range data.outputSteps {
|
||||||
|
if outputStep.Op == deploy.OpReplace {
|
||||||
|
step = outputStep
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
changesBuf := &bytes.Buffer{}
|
changesBuf := &bytes.Buffer{}
|
||||||
|
|
||||||
if step.Old != nil && step.New != nil && step.Old.Inputs != nil && step.New.Inputs != nil {
|
if step.Old != nil && step.New != nil && step.Old.Inputs != nil && step.New.Inputs != nil {
|
||||||
|
@ -356,7 +379,7 @@ func (data *resourceRowData) getInfoColumn() string {
|
||||||
appendDiagMessage(fmt.Sprintf("%v debug messages", diagInfo.DebugCount))
|
appendDiagMessage(fmt.Sprintf("%v debug messages", diagInfo.DebugCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !data.display.Done {
|
if !data.display.done {
|
||||||
// If we're not totally done, and we're in the tree-view also print out the worst diagnostic
|
// If we're not totally done, and we're in the tree-view also print out the worst diagnostic
|
||||||
// next to the status message. This is helpful for long running tasks to know what's going
|
// next to the status message. This is helpful for long running tasks to know what's going
|
||||||
// on. However, once done, we print the diagnostics at the bottom, so we don't need to show
|
// on. However, once done, we print the diagnostics at the bottom, so we don't need to show
|
||||||
|
|
Loading…
Reference in a new issue