From d429e8266146abd74b1153017ab6f8bd33080c1f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Nov 2016 14:46:26 -0700 Subject: [PATCH] command: show shadow errors to the user --- command/apply.go | 24 ++++++++++++++++++++++++ command/meta.go | 36 ++++++++++++++++++++++++++++++++++++ command/plan.go | 19 +++++++++++++++++++ command/refresh.go | 19 +++++++++++++++++++ 4 files changed, 98 insertions(+) diff --git a/command/apply.go b/command/apply.go index 57779143e..7a12197dc 100644 --- a/command/apply.go +++ b/command/apply.go @@ -136,6 +136,9 @@ func (c *ApplyCommand) Run(args []string) int { } } + // This is going to keep track of shadow errors + var shadowErr error + // Build the context based on the arguments given ctx, planned, err := c.Context(contextOpts{ Destroy: c.Destroy, @@ -189,6 +192,12 @@ func (c *ApplyCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) return 1 } + + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "input operation:")) + } } if !validateContext(ctx, c.Ui) { return 1 @@ -208,6 +217,12 @@ func (c *ApplyCommand) Run(args []string) int { "Error creating plan: %s", err)) return 1 } + + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "plan operation:")) + } } // Setup the state hook for continuous state updates @@ -229,6 +244,12 @@ func (c *ApplyCommand) Run(args []string) int { go func() { defer close(doneCh) state, applyErr = ctx.Apply() + + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "apply operation:")) + } }() // Wait for the apply to finish or for us to be interrupted so @@ -304,6 +325,9 @@ func (c *ApplyCommand) Run(args []string) int { } } + // If we have an error in the shadow graph, let the user know. + c.outputShadowError(shadowErr, applyErr == nil) + return 0 } diff --git a/command/meta.go b/command/meta.go index 92f25db1e..6c2b62852 100644 --- a/command/meta.go +++ b/command/meta.go @@ -460,6 +460,42 @@ func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) { } } +// outputShadowError outputs the error from ctx.ShadowError. If the +// error is nil then nothing happens. If output is false then it isn't +// outputted to the user (you can define logic to guard against outputting). +func (m *Meta) outputShadowError(err error, output bool) bool { + // Do nothing if no error + if err == nil { + return false + } + + // If not outputting, do nothing + if !output { + return false + } + + // Output! + m.Ui.Output(m.Colorize().Color(fmt.Sprintf( + "[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+ + "This is not an error. Your Terraform operation completed successfully.\n"+ + "Your real infrastructure is unaffected by this message.\n\n"+ + "[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+ + "background. These features cannot affect real state and never touch\n"+ + "real infrastructure. If the features work properly, you see nothing.\n"+ + "If the features fail, this message appears.\n\n"+ + "The following failures happened while running experimental features.\n"+ + "Please report a Terraform bug so that future Terraform versions that\n"+ + "enable these features can be improved!\n\n"+ + "You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+ + "%s\n\n"+ + "This is not an error. Your terraform operation completed successfully\n"+ + "and your real infrastructure is unaffected by this message.", + err, + ))) + + return true +} + // contextOpts are the options used to load a context from a command. type contextOpts struct { // Path to the directory where the root module is. diff --git a/command/plan.go b/command/plan.go index ffe83410a..46b22d813 100644 --- a/command/plan.go +++ b/command/plan.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/terraform" ) @@ -57,6 +58,9 @@ func (c *PlanCommand) Run(args []string) int { countHook := new(CountHook) c.Meta.extraHooks = []terraform.Hook{countHook} + // This is going to keep track of shadow errors + var shadowErr error + ctx, _, err := c.Context(contextOpts{ Destroy: destroy, Path: path, @@ -73,6 +77,12 @@ func (c *PlanCommand) Run(args []string) int { return 1 } + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "input operation:")) + } + if !validateContext(ctx, c.Ui) { return 1 } @@ -138,6 +148,15 @@ func (c *PlanCommand) Run(args []string) int { countHook.ToChange, countHook.ToRemove+countHook.ToRemoveAndAdd))) + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "plan operation:")) + } + + // If we have an error in the shadow graph, let the user know. + c.outputShadowError(shadowErr, true) + if detailed { return 2 } diff --git a/command/refresh.go b/command/refresh.go index 0f9206276..1b0a5cf60 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -6,6 +6,7 @@ import ( "os" "strings" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/terraform" ) @@ -79,6 +80,9 @@ func (c *RefreshCommand) Run(args []string) int { } } + // This is going to keep track of shadow errors + var shadowErr error + // Build the context based on the arguments given ctx, _, err := c.Context(contextOpts{ Path: configPath, @@ -95,6 +99,12 @@ func (c *RefreshCommand) Run(args []string) int { return 1 } + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "input operation:")) + } + if !validateContext(ctx, c.Ui) { return 1 } @@ -115,6 +125,15 @@ func (c *RefreshCommand) Run(args []string) int { c.Ui.Output(c.Colorize().Color(outputs)) } + // Record any shadow errors for later + if err := ctx.ShadowError(); err != nil { + shadowErr = multierror.Append(shadowErr, multierror.Prefix( + err, "refresh operation:")) + } + + // If we have an error in the shadow graph, let the user know. + c.outputShadowError(shadowErr, true) + return 0 }