Merge branch 'master' of github.com:hashicorp/terraform

* 'master' of github.com:hashicorp/terraform:
  Docs typo - s/instaces/instances
  Update CHANGELOG.md
  terraform: shadow graph uses GraphWalkerPanicwrap to catch errors
  terraform: GraphWalkerPanicwrap catches panics during graph walks
This commit is contained in:
clint shryock 2016-11-03 16:14:33 -05:00
commit 6244463ffb
7 changed files with 93 additions and 6 deletions

View File

@ -5,6 +5,7 @@ FEATURES:
* **New Resource:** `postgresql_extension` [GH-9210] * **New Resource:** `postgresql_extension` [GH-9210]
IMPROVEMENTS: IMPROVEMENTS:
* core: Improve shadow graph robustness by catching panics during graph evaluation. [GH-9852]
* provider/aws: Provide the option to skip_destroy on aws_volume_attachment [GH-9792] * provider/aws: Provide the option to skip_destroy on aws_volume_attachment [GH-9792]
* provider/aws: Allows aws_alb security_groups to be updated [GH-9804] * provider/aws: Allows aws_alb security_groups to be updated [GH-9804]
* provider/aws: Add the enable_sni attribute for Route53 health checks. [GH-9822] * provider/aws: Add the enable_sni attribute for Route53 health checks. [GH-9822]

View File

@ -736,11 +736,14 @@ func (c *Context) walk(
// If we have a shadow graph, wait for that to complete. // If we have a shadow graph, wait for that to complete.
if shadowCloser != nil { if shadowCloser != nil {
// Build the graph walker for the shadow. // Build the graph walker for the shadow. We also wrap this in
shadowWalker := &ContextGraphWalker{ // a panicwrap so that panics are captured. For the shadow graph,
// we just want panics to be normal errors rather than to crash
// Terraform.
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
Context: shadowCtx, Context: shadowCtx,
Operation: operation, Operation: operation,
} })
// Kick off the shadow walk. This will block on any operations // Kick off the shadow walk. This will block on any operations
// on the real walk so it is fine to start first. // on the real walk so it is fine to start first.

View File

@ -3,6 +3,7 @@ package terraform
import ( import (
"fmt" "fmt"
"log" "log"
"runtime/debug"
"strings" "strings"
"sync" "sync"
@ -217,11 +218,40 @@ func (g *Graph) walk(walker GraphWalker) error {
// Get the path for logs // Get the path for logs
path := strings.Join(ctx.Path(), ".") path := strings.Join(ctx.Path(), ".")
// Determine if our walker is a panic wrapper
panicwrap, ok := walker.(GraphWalkerPanicwrapper)
if !ok {
panicwrap = nil // just to be sure
}
// Walk the graph. // Walk the graph.
var walkFn dag.WalkFunc var walkFn dag.WalkFunc
walkFn = func(v dag.Vertex) (rerr error) { walkFn = func(v dag.Vertex) (rerr error) {
log.Printf("[DEBUG] vertex '%s.%s': walking", path, dag.VertexName(v)) log.Printf("[DEBUG] vertex '%s.%s': walking", path, dag.VertexName(v))
// If we have a panic wrap GraphWalker and a panic occurs, recover
// and call that. We ensure the return value is an error, however,
// so that future nodes are not called.
defer func() {
// If no panicwrap, do nothing
if panicwrap == nil {
return
}
// If no panic, do nothing
err := recover()
if err == nil {
return
}
// Modify the return value to show the error
rerr = fmt.Errorf("vertex %q captured panic: %s\n\n%s",
dag.VertexName(v), err, debug.Stack())
// Call the panic wrapper
panicwrap.Panic(v, err)
}()
walker.EnterVertex(v) walker.EnterVertex(v)
defer func() { walker.ExitVertex(v, rerr) }() defer func() { walker.ExitVertex(v, rerr) }()

View File

@ -69,6 +69,29 @@ func TestGraphReplace_DependableWithNonDependable(t *testing.T) {
} }
} }
func TestGraphWalk_panicWrap(t *testing.T) {
var g Graph
// Add our crasher
v := &testGraphSubPath{
PathFn: func() []string {
panic("yo")
},
}
g.Add(v)
err := g.Walk(GraphWalkerPanicwrap(new(NullGraphWalker)))
if err == nil {
t.Fatal("should error")
}
}
type testGraphSubPath struct {
PathFn func() []string
}
func (v *testGraphSubPath) Path() []string { return v.PathFn() }
type testGraphDependable struct { type testGraphDependable struct {
VertexName string VertexName string
DependentOnMock []string DependentOnMock []string

View File

@ -15,12 +15,42 @@ type GraphWalker interface {
ExitEvalTree(dag.Vertex, interface{}, error) error ExitEvalTree(dag.Vertex, interface{}, error) error
} }
// GrpahWalkerPanicwrapper can be optionally implemented to catch panics
// that occur while walking the graph. This is not generally recommended
// since panics should crash Terraform and result in a bug report. However,
// this is particularly useful for situations like the shadow graph where
// you don't ever want to cause a panic.
type GraphWalkerPanicwrapper interface {
GraphWalker
// Panic is called when a panic occurs. This will halt the panic from
// propogating so if the walker wants it to crash still it should panic
// again. This is called from within a defer so runtime/debug.Stack can
// be used to get the stack trace of the panic.
Panic(dag.Vertex, interface{})
}
// GraphWalkerPanicwrap wraps an existing Graphwalker to wrap and swallow
// the panics. This doesn't lose the panics since the panics are still
// returned as errors as part of a graph walk.
func GraphWalkerPanicwrap(w GraphWalker) GraphWalkerPanicwrapper {
return &graphWalkerPanicwrapper{
GraphWalker: w,
}
}
type graphWalkerPanicwrapper struct {
GraphWalker
}
func (graphWalkerPanicwrapper) Panic(dag.Vertex, interface{}) {}
// NullGraphWalker is a GraphWalker implementation that does nothing. // NullGraphWalker is a GraphWalker implementation that does nothing.
// This can be embedded within other GraphWalker implementations for easily // This can be embedded within other GraphWalker implementations for easily
// implementing all the required functions. // implementing all the required functions.
type NullGraphWalker struct{} type NullGraphWalker struct{}
func (NullGraphWalker) EnterPath([]string) EvalContext { return nil } func (NullGraphWalker) EnterPath([]string) EvalContext { return new(MockEvalContext) }
func (NullGraphWalker) ExitPath([]string) {} func (NullGraphWalker) ExitPath([]string) {}
func (NullGraphWalker) EnterVertex(dag.Vertex) {} func (NullGraphWalker) EnterVertex(dag.Vertex) {}
func (NullGraphWalker) ExitVertex(dag.Vertex, error) {} func (NullGraphWalker) ExitVertex(dag.Vertex, error) {}

View File

@ -14,7 +14,7 @@ Provides an Elastic Load Balancer resource.
provides both a standalone [ELB Attachment resource](elb_attachment.html) provides both a standalone [ELB Attachment resource](elb_attachment.html)
(describing an instance attached to an ELB), and an ELB resource with (describing an instance attached to an ELB), and an ELB resource with
`instances` defined in-line. At this time you cannot use an ELB with in-line `instances` defined in-line. At this time you cannot use an ELB with in-line
instaces in conjunction with a ELB Attachment resources. Doing so will cause a instances in conjunction with a ELB Attachment resources. Doing so will cause a
conflict and will overwrite attachments. conflict and will overwrite attachments.
## Example Usage ## Example Usage

View File

@ -14,7 +14,7 @@ Provides an Elastic Load Balancer Attachment resource.
both a standalone ELB Attachment resource (describing an instance attached to both a standalone ELB Attachment resource (describing an instance attached to
an ELB), and an [Elastic Load Balancer resource](elb.html) with an ELB), and an [Elastic Load Balancer resource](elb.html) with
`instances` defined in-line. At this time you cannot use an ELB with in-line `instances` defined in-line. At this time you cannot use an ELB with in-line
instaces in conjunction with an ELB Attachment resource. Doing so will cause a instances in conjunction with an ELB Attachment resource. Doing so will cause a
conflict and will overwrite attachments. conflict and will overwrite attachments.
## Example Usage ## Example Usage