diff --git a/cmd/apply.go b/cmd/apply.go index 01900ae06..d3022ad1a 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -5,6 +5,7 @@ package cmd import ( "bytes" "fmt" + "time" "github.com/spf13/cobra" @@ -34,6 +35,8 @@ func newApplyCmd() *cobra.Command { "a path to a blueprint elsewhere can be provided as the [blueprint] argument.", Run: func(cmd *cobra.Command, args []string) { if comp, plan := plan(cmd, args, delete); plan != nil { + // Create an object to track progress and perform the actual operations. + start := time.Now() progress := newProgress(detailed) if err, _, _ := plan.Apply(progress); err != nil { // TODO: we want richer diagnostics in the event that a plan apply fails. For instance, we want to @@ -41,6 +44,31 @@ func newApplyCmd() *cobra.Command { // probably want to plumb diag.Sink through apply so it can issue its own rich diagnostics. comp.Diag().Errorf(errors.ErrorPlanApplyFailed, err) } + + // Print out the total number of steps performed (and their kinds), if any succeeded. + var b bytes.Buffer + if progress.Steps > 0 { + b.WriteString(fmt.Sprintf("%v total operations in %v:\n", progress.Steps, time.Since(start))) + if c := progress.Ops[resource.OpCreate]; c > 0 { + b.WriteString(fmt.Sprintf(" %v%v resources created%v\n", + opPrefix(resource.OpCreate), c, colors.Reset)) + } + if c := progress.Ops[resource.OpUpdate]; c > 0 { + b.WriteString(fmt.Sprintf(" %v%v resources updated%v\n", + opPrefix(resource.OpUpdate), c, colors.Reset)) + } + if c := progress.Ops[resource.OpDelete]; c > 0 { + b.WriteString(fmt.Sprintf(" %v%v resources deleted%v\n", + opPrefix(resource.OpDelete), c, colors.Reset)) + } + } + if progress.MaybeCorrupt { + b.WriteString(fmt.Sprintf( + "%vfatal: A catastrophic error occurred; resources states may be unknown%v\n", + colors.Red, colors.Reset)) + } + s := b.String() + fmt.Printf(colors.Colorize(s)) } }, } @@ -58,28 +86,40 @@ func newApplyCmd() *cobra.Command { // applyProgress pretty-prints the plan application process as it goes. type applyProgress struct { - c int - detailed bool + Steps int + Ops map[resource.StepOp]int + MaybeCorrupt bool + Detailed bool } func newProgress(detailed bool) *applyProgress { - return &applyProgress{detailed: detailed} + return &applyProgress{ + Steps: 0, + Ops: make(map[resource.StepOp]int), + Detailed: detailed, + } } func (prog *applyProgress) Before(step resource.Step) { + // Print the step. var b bytes.Buffer - prog.c++ - b.WriteString(fmt.Sprintf("Applying step #%v\n", prog.c)) - printStep(&b, step, !prog.detailed, " ") + stepnum := prog.Steps + 1 + b.WriteString(fmt.Sprintf("Applying step #%v\n", stepnum)) + printStep(&b, step, !prog.Detailed, " ") s := colors.Colorize(b.String()) fmt.Printf(s) } func (prog *applyProgress) After(step resource.Step, err error, state resource.ResourceState) { - if err != nil { + if err == nil { + // Increment the counters. + prog.Steps++ + prog.Ops[step.Op()]++ + } else { var b bytes.Buffer // Print the state of the resource; we don't issue the error, because the apply above will do that. - b.WriteString(fmt.Sprintf("Step #%v failed: ", prog.c)) + stepnum := prog.Steps + 1 + b.WriteString(fmt.Sprintf("Step #%v failed: ", stepnum)) switch state { case resource.StateOK: b.WriteString(colors.BrightYellow) @@ -87,6 +127,7 @@ func (prog *applyProgress) After(step resource.Step, err error, state resource.R case resource.StateUnknown: b.WriteString(colors.BrightRed) b.WriteString("this failure was catastrophic and the provider cannot guarantee recovery") + prog.MaybeCorrupt = true default: contract.Failf("Unrecognized resource state: %v", state) } diff --git a/cmd/plan.go b/cmd/plan.go index 75737a6ca..3b060d9d4 100644 --- a/cmd/plan.go +++ b/cmd/plan.go @@ -67,18 +67,20 @@ func printPlan(plan resource.Plan, summary bool) { } } -func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) { - // First print out the operation. - switch step.Op() { +func opPrefix(op resource.StepOp) string { + switch op { case resource.OpCreate: - b.WriteString(colors.Green) - b.WriteString("+ ") + return colors.Green + "+ " case resource.OpDelete: - b.WriteString(colors.Red) - b.WriteString("- ") + return colors.Red + "- " default: - b.WriteString(" ") + return " " } +} + +func printStep(b *bytes.Buffer, step resource.Step, summary bool, indent string) { + // First print out the operation's prefix. + b.WriteString(opPrefix(step.Op())) // Next print the resource moniker, properties, etc. printResource(b, step.Resource(), summary, indent)