diff --git a/terraform/eval.go b/terraform/eval.go deleted file mode 100644 index 2a8909aee..000000000 --- a/terraform/eval.go +++ /dev/null @@ -1,62 +0,0 @@ -package terraform - -import ( - "log" - - "github.com/hashicorp/terraform/tfdiags" -) - -// EvalNode is the interface that must be implemented by graph nodes to -// evaluate/execute. -type EvalNode interface { - // Eval evaluates this node with the given context. The second parameter - // are the argument values. These will match in order and 1-1 with the - // results of the Args() return value. - Eval(EvalContext) (interface{}, error) -} - -// GraphNodeEvalable is the interface that graph nodes must implement -// to enable valuation. -type GraphNodeEvalable interface { - EvalTree() EvalNode -} - -// EvalEarlyExitError is a special error return value that can be returned -// by eval nodes that does an early exit. -type EvalEarlyExitError struct{} - -func (EvalEarlyExitError) Error() string { return "early exit" } - -// Eval evaluates the given EvalNode with the given context, properly -// evaluating all args in the correct order. -func Eval(n EvalNode, ctx EvalContext) (interface{}, error) { - // Call the lower level eval which doesn't understand early exit, - // and if we early exit, it isn't an error. - result, err := EvalRaw(n, ctx) - if err != nil { - if _, ok := err.(EvalEarlyExitError); ok { - return nil, nil - } - } - - return result, err -} - -// EvalRaw is like Eval except that it returns all errors, even if they -// signal something normal such as EvalEarlyExitError. -func EvalRaw(n EvalNode, ctx EvalContext) (interface{}, error) { - log.Printf("[TRACE] eval: %T", n) - output, err := n.Eval(ctx) - if err != nil { - switch err.(type) { - case EvalEarlyExitError: - log.Printf("[TRACE] eval: %T, early exit err: %s", n, err) - case tfdiags.NonFatalError: - log.Printf("[WARN] eval: %T, non-fatal err: %s", n, err) - default: - log.Printf("[ERROR] eval: %T, err: %s", n, err) - } - } - - return output, err -} diff --git a/terraform/eval_check_prevent_destroy.go b/terraform/eval_check_prevent_destroy.go deleted file mode 100644 index e3400fe4c..000000000 --- a/terraform/eval_check_prevent_destroy.go +++ /dev/null @@ -1,47 +0,0 @@ -package terraform - -import ( - "fmt" - - "github.com/hashicorp/terraform/plans" - - "github.com/hashicorp/hcl/v2" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/configs" - "github.com/hashicorp/terraform/tfdiags" -) - -// EvalPreventDestroy is an EvalNode implementation that returns an -// error if a resource has PreventDestroy configured and the diff -// would destroy the resource. -type EvalCheckPreventDestroy struct { - Addr addrs.ResourceInstance - Config *configs.Resource - Change **plans.ResourceInstanceChange -} - -func (n *EvalCheckPreventDestroy) Eval(ctx EvalContext) (interface{}, error) { - if n.Change == nil || *n.Change == nil || n.Config == nil || n.Config.Managed == nil { - return nil, nil - } - - change := *n.Change - preventDestroy := n.Config.Managed.PreventDestroy - - if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy { - var diags tfdiags.Diagnostics - diags = diags.Append(&hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Instance cannot be destroyed", - Detail: fmt.Sprintf( - "Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.", - n.Addr.Absolute(ctx.Path()).String(), - ), - Subject: &n.Config.DeclRange, - }) - return nil, diags.Err() - } - - return nil, nil -} diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index aec3fba97..49be3be1c 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -781,49 +781,6 @@ func (n *EvalReduceDiff) Eval(ctx EvalContext) (interface{}, error) { return nil, nil } -// EvalReadDiff is an EvalNode implementation that retrieves the planned -// change for a particular resource instance object. -type EvalReadDiff struct { - Addr addrs.ResourceInstance - DeposedKey states.DeposedKey - ProviderSchema **ProviderSchema - Change **plans.ResourceInstanceChange -} - -func (n *EvalReadDiff) Eval(ctx EvalContext) (interface{}, error) { - providerSchema := *n.ProviderSchema - changes := ctx.Changes() - addr := n.Addr.Absolute(ctx.Path()) - - schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource()) - if schema == nil { - // Should be caught during validation, so we don't bother with a pretty error here - return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) - } - - gen := states.CurrentGen - if n.DeposedKey != states.NotDeposed { - gen = n.DeposedKey - } - csrc := changes.GetResourceInstanceChange(addr, gen) - if csrc == nil { - log.Printf("[TRACE] EvalReadDiff: No planned change recorded for %s", addr) - return nil, nil - } - - change, err := csrc.Decode(schema.ImpliedType()) - if err != nil { - return nil, fmt.Errorf("failed to decode planned changes for %s: %s", addr, err) - } - if n.Change != nil { - *n.Change = change - } - - log.Printf("[TRACE] EvalReadDiff: Read %s change from plan for %s", change.Action, addr) - - return nil, nil -} - // EvalWriteDiff is an EvalNode implementation that saves a planned change // for an instance object into the set of global planned changes. type EvalWriteDiff struct { diff --git a/terraform/eval_error.go b/terraform/eval_error.go index 470f798b7..853ea2cc8 100644 --- a/terraform/eval_error.go +++ b/terraform/eval_error.go @@ -1,20 +1,7 @@ package terraform -// EvalReturnError is an EvalNode implementation that returns an -// error if it is present. -// -// This is useful for scenarios where an error has been captured by -// another EvalNode (like EvalApply) for special EvalTree-based error -// handling, and that handling has completed, so the error should be -// returned normally. -type EvalReturnError struct { - Error *error -} +// EvalEarlyExitError is a special error return value that can be returned +// by eval nodes that does an early exit. +type EvalEarlyExitError struct{} -func (n *EvalReturnError) Eval(ctx EvalContext) (interface{}, error) { - if n.Error == nil { - return nil, nil - } - - return nil, *n.Error -} +func (EvalEarlyExitError) Error() string { return "early exit" } diff --git a/terraform/eval_filter.go b/terraform/eval_filter.go deleted file mode 100644 index 711c625c8..000000000 --- a/terraform/eval_filter.go +++ /dev/null @@ -1,25 +0,0 @@ -package terraform - -// EvalNodeFilterFunc is the callback used to replace a node with -// another to node. To not do the replacement, just return the input node. -type EvalNodeFilterFunc func(EvalNode) EvalNode - -// EvalNodeFilterable is an interface that can be implemented by -// EvalNodes to allow filtering of sub-elements. Note that this isn't -// a common thing to implement and you probably don't need it. -type EvalNodeFilterable interface { - EvalNode - Filter(EvalNodeFilterFunc) -} - -// EvalFilter runs the filter on the given node and returns the -// final filtered value. This should be called rather than checking -// the EvalNode directly since this will properly handle EvalNodeFilterables. -func EvalFilter(node EvalNode, fn EvalNodeFilterFunc) EvalNode { - if f, ok := node.(EvalNodeFilterable); ok { - f.Filter(fn) - return node - } - - return fn(node) -} diff --git a/terraform/eval_filter_operation.go b/terraform/eval_filter_operation.go deleted file mode 100644 index 1a55f024a..000000000 --- a/terraform/eval_filter_operation.go +++ /dev/null @@ -1,49 +0,0 @@ -package terraform - -// EvalNodeOpFilterable is an interface that EvalNodes can implement -// to be filterable by the operation that is being run on Terraform. -type EvalNodeOpFilterable interface { - IncludeInOp(walkOperation) bool -} - -// EvalNodeFilterOp returns a filter function that filters nodes that -// include themselves in specific operations. -func EvalNodeFilterOp(op walkOperation) EvalNodeFilterFunc { - return func(n EvalNode) EvalNode { - include := true - if of, ok := n.(EvalNodeOpFilterable); ok { - include = of.IncludeInOp(op) - } - if include { - return n - } - - return EvalNoop{} - } -} - -// EvalOpFilter is an EvalNode implementation that is a proxy to -// another node but filters based on the operation. -type EvalOpFilter struct { - // Ops is the list of operations to include this node in. - Ops []walkOperation - - // Node is the node to execute - Node EvalNode -} - -// TODO: test -func (n *EvalOpFilter) Eval(ctx EvalContext) (interface{}, error) { - return EvalRaw(n.Node, ctx) -} - -// EvalNodeOpFilterable impl. -func (n *EvalOpFilter) IncludeInOp(op walkOperation) bool { - for _, v := range n.Ops { - if v == op { - return true - } - } - - return false -} diff --git a/terraform/eval_if.go b/terraform/eval_if.go deleted file mode 100644 index d6b46a1f2..000000000 --- a/terraform/eval_if.go +++ /dev/null @@ -1,26 +0,0 @@ -package terraform - -// EvalIf is an EvalNode that is a conditional. -type EvalIf struct { - If func(EvalContext) (bool, error) - Then EvalNode - Else EvalNode -} - -// TODO: test -func (n *EvalIf) Eval(ctx EvalContext) (interface{}, error) { - yes, err := n.If(ctx) - if err != nil { - return nil, err - } - - if yes { - return EvalRaw(n.Then, ctx) - } else { - if n.Else != nil { - return EvalRaw(n.Else, ctx) - } - } - - return nil, nil -} diff --git a/terraform/eval_noop.go b/terraform/eval_noop.go deleted file mode 100644 index f4bc8225c..000000000 --- a/terraform/eval_noop.go +++ /dev/null @@ -1,8 +0,0 @@ -package terraform - -// EvalNoop is an EvalNode that does nothing. -type EvalNoop struct{} - -func (EvalNoop) Eval(EvalContext) (interface{}, error) { - return nil, nil -} diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index 3e425e910..0af5964ea 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -60,42 +60,3 @@ func GetProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Inter schema := ctx.ProviderSchema(addr) return provider, schema, nil } - -// EvalGetProvider is an EvalNode implementation that retrieves an already -// initialized provider instance for the given name. -// -// Unlike most eval nodes, this takes an _absolute_ provider configuration, -// because providers can be passed into and inherited between modules. -// Resource nodes must therefore know the absolute path of the provider they -// will use, which is usually accomplished by implementing -// interface GraphNodeProviderConsumer. -type EvalGetProvider struct { - Addr addrs.AbsProviderConfig - Output *providers.Interface - - // If non-nil, Schema will be updated after eval to refer to the - // schema of the provider. - Schema **ProviderSchema -} - -func (n *EvalGetProvider) Eval(ctx EvalContext) (interface{}, error) { - if n.Addr.Provider.Type == "" { - // Should never happen - panic("EvalGetProvider used with uninitialized provider configuration address") - } - - result := ctx.Provider(n.Addr) - if result == nil { - return nil, fmt.Errorf("provider %s not initialized", n.Addr) - } - - if n.Output != nil { - *n.Output = result - } - - if n.Schema != nil { - *n.Schema = ctx.ProviderSchema(n.Addr) - } - - return nil, nil -} diff --git a/terraform/eval_provider_test.go b/terraform/eval_provider_test.go index a029f3677..ff1eb8ab3 100644 --- a/terraform/eval_provider_test.go +++ b/terraform/eval_provider_test.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/providers" ) func TestBuildProviderConfig(t *testing.T) { @@ -54,30 +53,3 @@ func TestBuildProviderConfig(t *testing.T) { t.Fatalf("incorrect merged config\ngot: %#v\nwant: %#v", got, want) } } - -func TestEvalGetProvider_impl(t *testing.T) { - var _ EvalNode = new(EvalGetProvider) -} - -func TestEvalGetProvider(t *testing.T) { - var actual providers.Interface - n := &EvalGetProvider{ - Addr: addrs.RootModuleInstance.ProviderConfigDefault(addrs.NewDefaultProvider("foo")), - Output: &actual, - } - provider := &MockProvider{} - ctx := &MockEvalContext{ProviderProvider: provider} - if _, err := n.Eval(ctx); err != nil { - t.Fatalf("err: %s", err) - } - if actual != provider { - t.Fatalf("bad: %#v", actual) - } - - if !ctx.ProviderCalled { - t.Fatal("should be called") - } - if ctx.ProviderAddr.String() != `provider["registry.terraform.io/hashicorp/foo"]` { - t.Fatalf("wrong provider address %s", ctx.ProviderAddr) - } -} diff --git a/terraform/eval_sequence.go b/terraform/eval_sequence.go deleted file mode 100644 index 3485e4f14..000000000 --- a/terraform/eval_sequence.go +++ /dev/null @@ -1,42 +0,0 @@ -package terraform - -import ( - "github.com/hashicorp/terraform/tfdiags" -) - -// EvalSequence is an EvalNode that evaluates in sequence. -type EvalSequence struct { - Nodes []EvalNode -} - -func (n *EvalSequence) Eval(ctx EvalContext) (interface{}, error) { - var diags tfdiags.Diagnostics - - for _, n := range n.Nodes { - if n == nil { - continue - } - - if _, err := EvalRaw(n, ctx); err != nil { - if _, isEarlyExit := err.(EvalEarlyExitError); isEarlyExit { - // In this path we abort early, losing any non-error - // diagnostics we saw earlier. - return nil, err - } - diags = diags.Append(err) - if diags.HasErrors() { - // Halt if we get some errors, but warnings are okay. - break - } - } - } - - return nil, diags.ErrWithWarnings() -} - -// EvalNodeFilterable impl. -func (n *EvalSequence) Filter(fn EvalNodeFilterFunc) { - for i, node := range n.Nodes { - n.Nodes[i] = fn(node) - } -} diff --git a/terraform/eval_sequence_test.go b/terraform/eval_sequence_test.go deleted file mode 100644 index 972f0cd6f..000000000 --- a/terraform/eval_sequence_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package terraform - -import ( - "testing" -) - -func TestEvalSequence_impl(t *testing.T) { - var _ EvalNodeFilterable = new(EvalSequence) -} diff --git a/terraform/eval_state.go b/terraform/eval_state.go index aaf6fecfe..1edab4516 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -149,35 +149,7 @@ func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) { return obj, nil } -// EvalUpdateStateHook is an EvalNode implementation that calls the -// PostStateUpdate hook with the current state. -type EvalUpdateStateHook struct{} - -func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) { - // In principle we could grab the lock here just long enough to take a - // deep copy and then pass that to our hooks below, but we'll instead - // hold the hook for the duration to avoid the potential confusing - // situation of us racing to call PostStateUpdate concurrently with - // different state snapshots. - stateSync := ctx.State() - state := stateSync.Lock().DeepCopy() - defer stateSync.Unlock() - - // Call the hook - err := ctx.Hook(func(h Hook) (HookAction, error) { - return h.PostStateUpdate(state) - }) - if err != nil { - return nil, err - } - - return nil, nil -} - // UpdateStateHook calls the PostStateUpdate hook with the current state. -// -// TODO: UpdateStateHook will eventually replace EvalUpdateStateHook, at which -// point EvalUpdateStateHook can be removed and this comment updated. func UpdateStateHook(ctx EvalContext) error { // In principle we could grab the lock here just long enough to take a // deep copy and then pass that to our hooks below, but we'll instead diff --git a/terraform/eval_state_test.go b/terraform/eval_state_test.go index 6d6425a54..a9c73cded 100644 --- a/terraform/eval_state_test.go +++ b/terraform/eval_state_test.go @@ -12,29 +12,6 @@ import ( "github.com/hashicorp/terraform/states" ) -func TestEvalUpdateStateHook(t *testing.T) { - mockHook := new(MockHook) - - state := states.NewState() - state.Module(addrs.RootModuleInstance).SetLocalValue("foo", cty.StringVal("hello")) - - ctx := new(MockEvalContext) - ctx.HookHook = mockHook - ctx.StateState = state.SyncWrapper() - - node := &EvalUpdateStateHook{} - if _, err := node.Eval(ctx); err != nil { - t.Fatalf("err: %s", err) - } - - if !mockHook.PostStateUpdateCalled { - t.Fatal("should call PostStateUpdate") - } - if mockHook.PostStateUpdateState.LocalValue(addrs.LocalValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)) != cty.StringVal("hello") { - t.Fatalf("wrong state passed to hook: %s", spew.Sdump(mockHook.PostStateUpdateState)) - } -} - func TestEvalReadState(t *testing.T) { var output *states.ResourceInstanceObject mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{ @@ -50,7 +27,7 @@ func TestEvalReadState(t *testing.T) { cases := map[string]struct { Resources map[string]*ResourceState - Node EvalNode + Node *EvalReadState ExpectedInstanceId string }{ "ReadState gets primary instance state": { @@ -74,6 +51,59 @@ func TestEvalReadState(t *testing.T) { }, ExpectedInstanceId: "i-abc123", }, + } + + for k, c := range cases { + t.Run(k, func(t *testing.T) { + ctx := new(MockEvalContext) + state := MustShimLegacyState(&State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: c.Resources, + }, + }, + }) + ctx.StateState = state.SyncWrapper() + ctx.PathPath = addrs.RootModuleInstance + + result, err := c.Node.Eval(ctx) + if err != nil { + t.Fatalf("[%s] Got err: %#v", k, err) + } + + expected := c.ExpectedInstanceId + if !(result != nil && instanceObjectIdForTests(result.(*states.ResourceInstanceObject)) == expected) { + t.Fatalf("[%s] Expected return with ID %#v, got: %#v", k, expected, result) + } + + if !(output != nil && output.Value.GetAttr("id") == cty.StringVal(expected)) { + t.Fatalf("[%s] Expected output with ID %#v, got: %#v", k, expected, output) + } + + output = nil + }) + } +} + +func TestEvalReadStateDeposed(t *testing.T) { + var output *states.ResourceInstanceObject + mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + }, + }, + }) + providerSchema := mockProvider.GetSchemaReturn + provider := providers.Interface(mockProvider) + + cases := map[string]struct { + Resources map[string]*ResourceState + Node *EvalReadStateDeposed + ExpectedInstanceId string + }{ "ReadStateDeposed gets deposed instance": { Resources: map[string]*ResourceState{ "aws_instance.bar": &ResourceState{ @@ -97,7 +127,6 @@ func TestEvalReadState(t *testing.T) { ExpectedInstanceId: "i-abc123", }, } - for k, c := range cases { t.Run(k, func(t *testing.T) { ctx := new(MockEvalContext) @@ -225,9 +254,6 @@ aws_instance.foo: (1 deposed) `) } -// Same test as TestEvalUpdateStateHook, similar function, slightly different -// signature. The EvalUpdateStateHook test and function will be removed when the -// EvalNode Removal is complete. func TestUpdateStateHook(t *testing.T) { mockHook := new(MockHook) diff --git a/terraform/eval_test.go b/terraform/eval_test.go deleted file mode 100644 index 29ae25843..000000000 --- a/terraform/eval_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package terraform - -import ( - "testing" -) - -func TestMockEvalContext_impl(t *testing.T) { - var _ EvalContext = new(MockEvalContext) -} - -func TestEval(t *testing.T) { - var result int - n := &testEvalAdd{ - Items: []int{10, 32}, - Result: &result, - } - - if _, err := Eval(n, nil); err != nil { - t.Fatalf("err: %s", err) - } - - if result != 42 { - t.Fatalf("bad: %#v", result) - } -} - -type testEvalAdd struct { - Items []int - Result *int -} - -func (n *testEvalAdd) Eval(ctx EvalContext) (interface{}, error) { - result := 0 - for _, item := range n.Items { - result += item - } - - *n.Result = result - return nil, nil -} diff --git a/terraform/graph.go b/terraform/graph.go index df1ecf134..73996c1f7 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -1,7 +1,6 @@ package terraform import ( - "fmt" "log" "github.com/hashicorp/terraform/tfdiags" @@ -57,38 +56,12 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { // If the node is exec-able, then execute it. if ev, ok := v.(GraphNodeExecutable); ok { - // A node must not be both Evalable and Executable. This will be - // removed when GraphNodeEvalable is fully removed. - if _, ok := v.(GraphNodeEvalable); ok { - panic(fmt.Sprintf( - "%T implements both GraphNodeEvalable and GraphNodeExecutable", v, - )) - } diags = diags.Append(walker.Execute(vertexCtx, ev)) if diags.HasErrors() { return } } - // If the node is eval-able, then evaluate it. - if ev, ok := v.(GraphNodeEvalable); ok { - tree := ev.EvalTree() - if tree == nil { - panic(fmt.Sprintf("%q (%T): nil eval tree", dag.VertexName(v), v)) - } - - // Allow the walker to change our tree if needed. Eval, - // then callback with the output. - log.Printf("[TRACE] vertex %q: evaluating", dag.VertexName(v)) - - tree = walker.EnterEvalTree(v, tree) - output, err := Eval(tree, vertexCtx) - diags = diags.Append(walker.ExitEvalTree(v, output, err)) - if diags.HasErrors() { - return - } - } - // If the node is dynamically expanded, then expand it if ev, ok := v.(GraphNodeDynamicExpandable); ok { log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v)) diff --git a/terraform/graph_walk.go b/terraform/graph_walk.go index 1ec099b3c..4ede23513 100644 --- a/terraform/graph_walk.go +++ b/terraform/graph_walk.go @@ -2,7 +2,6 @@ package terraform import ( "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/tfdiags" ) @@ -12,8 +11,6 @@ type GraphWalker interface { EvalContext() EvalContext EnterPath(addrs.ModuleInstance) EvalContext ExitPath(addrs.ModuleInstance) - EnterEvalTree(dag.Vertex, EvalNode) EvalNode - ExitEvalTree(dag.Vertex, interface{}, error) tfdiags.Diagnostics Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics } @@ -22,11 +19,7 @@ type GraphWalker interface { // implementing all the required functions. type NullGraphWalker struct{} -func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) } -func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) } -func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {} -func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return n } -func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) tfdiags.Diagnostics { - return nil -} +func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) } +func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) } +func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {} func (NullGraphWalker) Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics { return nil } diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index d3ca73b3f..c7492ed06 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -2,14 +2,12 @@ package terraform import ( "context" - "log" "sync" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs/configschema" - "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/instances" "github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/providers" @@ -106,49 +104,6 @@ func (w *ContextGraphWalker) EvalContext() EvalContext { return ctx } -func (w *ContextGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { - log.Printf("[TRACE] [%s] Entering eval tree: %s", w.Operation, dag.VertexName(v)) - - // Acquire a lock on the semaphore - w.Context.parallelSem.Acquire() - - // We want to filter the evaluation tree to only include operations - // that belong in this operation. - return EvalFilter(n, EvalNodeFilterOp(w.Operation)) -} - -func (w *ContextGraphWalker) ExitEvalTree(v dag.Vertex, output interface{}, err error) tfdiags.Diagnostics { - log.Printf("[TRACE] [%s] Exiting eval tree: %s", w.Operation, dag.VertexName(v)) - - // Release the semaphore - w.Context.parallelSem.Release() - - if err == nil { - return nil - } - - // Acquire the lock because anything is going to require a lock. - w.errorLock.Lock() - defer w.errorLock.Unlock() - - // If the error is non-fatal then we'll accumulate its diagnostics in our - // non-fatal list, rather than returning it directly, so that the graph - // walk can continue. - if nferr, ok := err.(tfdiags.NonFatalError); ok { - log.Printf("[WARN] %s: %s", dag.VertexName(v), nferr) - w.NonFatalDiagnostics = w.NonFatalDiagnostics.Append(nferr.Diagnostics) - return nil - } - - // Otherwise, we'll let our usual diagnostics machinery figure out how to - // unpack this as one or more diagnostic messages and return that. If we - // get down here then the returned diagnostics will contain at least one - // error, causing the graph walk to halt. - var diags tfdiags.Diagnostics - diags = diags.Append(err) - return diags -} - func (w *ContextGraphWalker) init() { w.contexts = make(map[string]*BuiltinEvalContext) w.providerCache = make(map[string]providers.Interface) diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index 561ee6a6c..c6fdc19b0 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -498,6 +498,8 @@ func (n *NodeAbstractResource) WriteResourceState(ctx EvalContext, addr addrs.Ab return nil } +// ReadResourceInstanceState reads the current object for a specific instance in +// the state. func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, error) { provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) @@ -540,12 +542,40 @@ func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr a return obj, nil } -// CheckPreventDestroy returns an error if a resource has PreventDestroy -// configured and the diff would destroy the resource. -func (n *NodeAbstractResource) CheckPreventDestroy(addr addrs.AbsResourceInstance, change *plans.ResourceInstanceChange) error { +// ReadDiff returns the planned change for a particular resource instance +// object. +func (n *NodeAbstractResourceInstance) ReadDiff(ctx EvalContext, providerSchema *ProviderSchema) (*plans.ResourceInstanceChange, error) { + changes := ctx.Changes() + addr := n.ResourceInstanceAddr() + + schema, _ := providerSchema.SchemaForResourceAddr(addr.Resource.Resource) + if schema == nil { + // Should be caught during validation, so we don't bother with a pretty error here + return nil, fmt.Errorf("provider does not support resource type %q", addr.Resource.Resource.Type) + } + + gen := states.CurrentGen + csrc := changes.GetResourceInstanceChange(addr, gen) + if csrc == nil { + log.Printf("[TRACE] EvalReadDiff: No planned change recorded for %s", n.Addr) + return nil, nil + } + + change, err := csrc.Decode(schema.ImpliedType()) + if err != nil { + return nil, fmt.Errorf("failed to decode planned changes for %s: %s", n.Addr, err) + } + + log.Printf("[TRACE] EvalReadDiff: Read %s change from plan for %s", change.Action, n.Addr) + + return change, nil +} + +func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.ResourceInstanceChange) error { if change == nil || n.Config == nil || n.Config.Managed == nil { return nil } + preventDestroy := n.Config.Managed.PreventDestroy if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy { @@ -555,7 +585,7 @@ func (n *NodeAbstractResource) CheckPreventDestroy(addr addrs.AbsResourceInstanc Summary: "Instance cannot be destroyed", Detail: fmt.Sprintf( "Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.", - addr, + n.Addr.String(), ), Subject: &n.Config.DeclRange, }) diff --git a/terraform/node_resource_apply_instance.go b/terraform/node_resource_apply_instance.go index adc8b1348..7ead676b1 100644 --- a/terraform/node_resource_apply_instance.go +++ b/terraform/node_resource_apply_instance.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) @@ -34,7 +33,7 @@ var ( _ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil) _ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil) _ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil) - _ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil) + _ GraphNodeExecutable = (*NodeApplyableResourceInstance)(nil) _ GraphNodeAttachDependencies = (*NodeApplyableResourceInstance)(nil) ) @@ -101,8 +100,8 @@ func (n *NodeApplyableResourceInstance) AttachDependencies(deps []addrs.ConfigRe n.Dependencies = deps } -// GraphNodeEvalable -func (n *NodeApplyableResourceInstance) EvalTree() EvalNode { +// GraphNodeExecutable +func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error { addr := n.ResourceInstanceAddr() if n.Config == nil { @@ -120,319 +119,334 @@ func (n *NodeApplyableResourceInstance) EvalTree() EvalNode { addr, ), )) - err := diags.Err() - return &EvalReturnError{ - Error: &err, - } + return diags.Err() } // Eval info is different depending on what kind of resource this is switch n.Config.Mode { case addrs.ManagedResourceMode: - return n.evalTreeManagedResource(addr) + return n.managedResourceExecute(ctx) case addrs.DataResourceMode: - return n.evalTreeDataResource(addr) + return n.dataResourceExecute(ctx) default: panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) } } -func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance) EvalNode { - var provider providers.Interface - var providerSchema *ProviderSchema - var change *plans.ResourceInstanceChange +func (n *NodeApplyableResourceInstance) dataResourceExecute(ctx EvalContext) error { + addr := n.ResourceInstanceAddr().Resource + + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err + } + + change, err := n.ReadDiff(ctx, providerSchema) + if err != nil { + return err + } + // Stop early if we don't actually have a diff + if change == nil { + return EvalEarlyExitError{} + } + + // In this particular call to EvalReadData we include our planned + // change, which signals that we expect this read to complete fully + // with no unknown values; it'll produce an error if not. var state *states.ResourceInstanceObject - - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - - // Get the saved diff for apply - &EvalReadDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &change, - }, - - // Stop early if we don't actually have a diff - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - if change == nil { - return true, EvalEarlyExitError{} - } - return true, nil - }, - Then: EvalNoop{}, - }, - - // In this particular call to EvalReadData we include our planned - // change, which signals that we expect this read to complete fully - // with no unknown values; it'll produce an error if not. - &evalReadDataApply{ - evalReadData{ - Addr: addr.Resource, - Config: n.Config, - Planned: &change, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - State: &state, - }, - }, - - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - }, - - // Clear the diff now that we've applied it, so - // later nodes won't see a diff that's now a no-op. - &EvalWriteDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: nil, - }, - - &EvalUpdateStateHook{}, + readDataApply := &evalReadDataApply{ + evalReadData{ + Addr: addr, + Config: n.Config, + Planned: &change, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + State: &state, }, } + _, err = readDataApply.Eval(ctx) + if err != nil { + return err + } + + writeState := &EvalWriteState{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + } + _, err = writeState.Eval(ctx) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr, + ProviderSchema: &providerSchema, + Change: nil, + } + _, err = writeDiff.Eval(ctx) + if err != nil { + return err + } + + UpdateStateHook(ctx) + return nil } -func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance) EvalNode { +func (n *NodeApplyableResourceInstance) managedResourceExecute(ctx EvalContext) error { // Declare a bunch of variables that are used for state during // evaluation. Most of this are written to by-address below. - var provider providers.Interface - var providerSchema *ProviderSchema - var diff, diffApply *plans.ResourceInstanceChange var state *states.ResourceInstanceObject - var err error var createNew bool var createBeforeDestroyEnabled bool var deposedKey states.DeposedKey - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - - // Get the saved diff for apply - &EvalReadDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &diffApply, - }, - - // We don't want to do any destroys - // (these are handled by NodeDestroyResourceInstance instead) - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - if diffApply == nil { - return true, EvalEarlyExitError{} - } - if diffApply.Action == plans.Delete { - return true, EvalEarlyExitError{} - } - return true, nil - }, - Then: EvalNoop{}, - }, - - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - destroy := false - if diffApply != nil { - destroy = (diffApply.Action == plans.Delete || diffApply.Action.IsReplace()) - - // Get the stored action for CBD if we have a plan already - createBeforeDestroyEnabled = diffApply.Change.Action == plans.CreateThenDelete - } - - if destroy && n.CreateBeforeDestroy() { - createBeforeDestroyEnabled = true - } - - return createBeforeDestroyEnabled, nil - }, - Then: &EvalDeposeState{ - Addr: addr.Resource, - ForceKey: n.PreallocatedDeposedKey, - OutputKey: &deposedKey, - }, - }, - - &EvalReadState{ - Addr: addr.Resource, - Provider: &provider, - ProviderSchema: &providerSchema, - - Output: &state, - }, - - // Get the saved diff - &EvalReadDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &diff, - }, - - // Make a new diff, in case we've learned new values in the state - // during apply which we can now incorporate. - &EvalDiff{ - Addr: addr.Resource, - Config: n.Config, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - State: &state, - PreviousDiff: &diff, - OutputChange: &diffApply, - OutputState: &state, - }, - - // Compare the diffs - &EvalCheckPlannedChange{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - Planned: &diff, - Actual: &diffApply, - }, - - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadState{ - Addr: addr.Resource, - Provider: &provider, - ProviderSchema: &providerSchema, - - Output: &state, - }, - - &EvalReduceDiff{ - Addr: addr.Resource, - InChange: &diffApply, - Destroy: false, - OutChange: &diffApply, - }, - - // EvalReduceDiff may have simplified our planned change - // into a NoOp if it only requires destroying, since destroying - // is handled by NodeDestroyResourceInstance. - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - if diffApply == nil || diffApply.Action == plans.NoOp { - return true, EvalEarlyExitError{} - } - return true, nil - }, - Then: EvalNoop{}, - }, - - // Call pre-apply hook - &EvalApplyPre{ - Addr: addr.Resource, - State: &state, - Change: &diffApply, - }, - &EvalApply{ - Addr: addr.Resource, - Config: n.Config, - State: &state, - Change: &diffApply, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - Output: &state, - Error: &err, - CreateNew: &createNew, - CreateBeforeDestroy: n.CreateBeforeDestroy(), - }, - &EvalMaybeTainted{ - Addr: addr.Resource, - State: &state, - Change: &diffApply, - Error: &err, - }, - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - Dependencies: &n.Dependencies, - }, - &EvalApplyProvisioners{ - Addr: addr.Resource, - State: &state, // EvalApplyProvisioners will skip if already tainted - ResourceConfig: n.Config, - CreateNew: &createNew, - Error: &err, - When: configs.ProvisionerWhenCreate, - }, - &EvalMaybeTainted{ - Addr: addr.Resource, - State: &state, - Change: &diffApply, - Error: &err, - }, - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - Dependencies: &n.Dependencies, - }, - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - return createBeforeDestroyEnabled && err != nil, nil - }, - Then: &EvalMaybeRestoreDeposedObject{ - Addr: addr.Resource, - PlannedChange: &diffApply, - Key: &deposedKey, - }, - }, - - // We clear the diff out here so that future nodes - // don't see a diff that is already complete. There - // is no longer a diff! - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - if !diff.Action.IsReplace() { - return true, nil - } - if !n.CreateBeforeDestroy() { - return true, nil - } - return false, nil - }, - Then: &EvalWriteDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: nil, - }, - }, - - &EvalApplyPost{ - Addr: addr.Resource, - State: &state, - Error: &err, - }, - &EvalUpdateStateHook{}, - }, + addr := n.ResourceInstanceAddr().Resource + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err } + + // Get the saved diff for apply + diffApply, err := n.ReadDiff(ctx, providerSchema) + if err != nil { + return err + } + + // We don't want to do any destroys + // (these are handled by NodeDestroyResourceInstance instead) + if diffApply == nil || diffApply.Action == plans.Delete { + return EvalEarlyExitError{} + } + + destroy := (diffApply.Action == plans.Delete || diffApply.Action.IsReplace()) + // Get the stored action for CBD if we have a plan already + createBeforeDestroyEnabled = diffApply.Change.Action == plans.CreateThenDelete + + if destroy && n.CreateBeforeDestroy() { + createBeforeDestroyEnabled = true + } + + if createBeforeDestroyEnabled { + deposeState := &EvalDeposeState{ + Addr: addr, + ForceKey: n.PreallocatedDeposedKey, + OutputKey: &deposedKey, + } + _, err = deposeState.Eval(ctx) + if err != nil { + return err + } + } + + readState := &EvalReadState{ + Addr: addr, + Provider: &provider, + ProviderSchema: &providerSchema, + + Output: &state, + } + _, err = readState.Eval(ctx) + if err != nil { + return err + } + + // Get the saved diff + diff, err := n.ReadDiff(ctx, providerSchema) + if err != nil { + return err + } + + // Make a new diff, in case we've learned new values in the state + // during apply which we can now incorporate. + evalDiff := &EvalDiff{ + Addr: addr, + Config: n.Config, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + State: &state, + PreviousDiff: &diff, + OutputChange: &diffApply, + OutputState: &state, + } + _, err = evalDiff.Eval(ctx) + if err != nil { + return err + } + + // Compare the diffs + checkPlannedChange := &EvalCheckPlannedChange{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + Planned: &diff, + Actual: &diffApply, + } + _, err = checkPlannedChange.Eval(ctx) + if err != nil { + return err + } + + readState = &EvalReadState{ + Addr: addr, + Provider: &provider, + ProviderSchema: &providerSchema, + + Output: &state, + } + _, err = readState.Eval(ctx) + if err != nil { + return err + } + + reduceDiff := &EvalReduceDiff{ + Addr: addr, + InChange: &diffApply, + Destroy: false, + OutChange: &diffApply, + } + _, err = reduceDiff.Eval(ctx) + if err != nil { + return err + } + + // EvalReduceDiff may have simplified our planned change + // into a NoOp if it only requires destroying, since destroying + // is handled by NodeDestroyResourceInstance. + if diffApply == nil || diffApply.Action == plans.NoOp { + return EvalEarlyExitError{} + } + + evalApplyPre := &EvalApplyPre{ + Addr: addr, + State: &state, + Change: &diffApply, + } + _, err = evalApplyPre.Eval(ctx) + if err != nil { + return err + } + + var applyError error + evalApply := &EvalApply{ + Addr: addr, + Config: n.Config, + State: &state, + Change: &diffApply, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + Output: &state, + Error: &applyError, + CreateNew: &createNew, + CreateBeforeDestroy: n.CreateBeforeDestroy(), + } + _, err = evalApply.Eval(ctx) + if err != nil { + return err + } + + evalMaybeTainted := &EvalMaybeTainted{ + Addr: addr, + State: &state, + Change: &diffApply, + Error: &applyError, + } + _, err = evalMaybeTainted.Eval(ctx) + if err != nil { + return err + } + + writeState := &EvalWriteState{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + Dependencies: &n.Dependencies, + } + _, err = writeState.Eval(ctx) + if err != nil { + return err + } + + applyProvisioners := &EvalApplyProvisioners{ + Addr: addr, + State: &state, // EvalApplyProvisioners will skip if already tainted + ResourceConfig: n.Config, + CreateNew: &createNew, + Error: &applyError, + When: configs.ProvisionerWhenCreate, + } + _, err = applyProvisioners.Eval(ctx) + if err != nil { + return err + } + + evalMaybeTainted = &EvalMaybeTainted{ + Addr: addr, + State: &state, + Change: &diffApply, + Error: &applyError, + } + _, err = evalMaybeTainted.Eval(ctx) + if err != nil { + return err + } + + writeState = &EvalWriteState{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + Dependencies: &n.Dependencies, + } + _, err = writeState.Eval(ctx) + if err != nil { + return err + } + + if createBeforeDestroyEnabled && applyError != nil { + maybeRestoreDesposedObject := &EvalMaybeRestoreDeposedObject{ + Addr: addr, + PlannedChange: &diffApply, + Key: &deposedKey, + } + _, err := maybeRestoreDesposedObject.Eval(ctx) + if err != nil { + return err + } + } + + // We clear the diff out here so that future nodes don't see a diff that is + // already complete. There is no longer a diff! + if !diff.Action.IsReplace() || !n.CreateBeforeDestroy() { + writeDiff := &EvalWriteDiff{ + Addr: addr, + ProviderSchema: &providerSchema, + Change: nil, + } + _, err := writeDiff.Eval(ctx) + if err != nil { + return err + } + } + + applyPost := &EvalApplyPost{ + Addr: addr, + State: &state, + Error: &applyError, + } + _, err = applyPost.Eval(ctx) + if err != nil { + return err + } + + UpdateStateHook(ctx) + return nil } diff --git a/terraform/node_resource_destroy.go b/terraform/node_resource_destroy.go index 7b1a2f580..d07b2c2e9 100644 --- a/terraform/node_resource_destroy.go +++ b/terraform/node_resource_destroy.go @@ -143,12 +143,7 @@ func (n *NodeDestroyResourceInstance) Execute(ctx EvalContext, op walkOperation) return err } - evalReadDiff := &EvalReadDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &changeApply, - } - _, err = evalReadDiff.Eval(ctx) + changeApply, err = n.ReadDiff(ctx, providerSchema) if err != nil { return err } diff --git a/terraform/node_resource_destroy_deposed.go b/terraform/node_resource_destroy_deposed.go index 550cffea5..6d5af049b 100644 --- a/terraform/node_resource_destroy_deposed.go +++ b/terraform/node_resource_destroy_deposed.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" ) @@ -37,7 +36,7 @@ var ( _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeExecutable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) ) @@ -64,55 +63,58 @@ func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference } // GraphNodeEvalable impl. -func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode { +func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error { addr := n.ResourceInstanceAddr() - var provider providers.Interface - var providerSchema *ProviderSchema - var state *states.ResourceInstanceObject - - seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err + } // During the plan walk we always produce a planned destroy change, because // destroying is the only supported action for deposed objects. var change *plans.ResourceInstanceChange - seq.Nodes = append(seq.Nodes, &EvalOpFilter{ - Ops: []walkOperation{walkPlan, walkPlanDestroy}, - Node: &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadStateDeposed{ - Addr: addr.Resource, - Output: &state, - Key: n.DeposedKey, - Provider: &provider, - ProviderSchema: &providerSchema, - }, - &EvalDiffDestroy{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - DeposedKey: n.DeposedKey, - State: &state, - Output: &change, - }, - &EvalWriteDiff{ - Addr: addr.Resource, - DeposedKey: n.DeposedKey, - ProviderSchema: &providerSchema, - Change: &change, - }, - // Since deposed objects cannot be referenced by expressions - // elsewhere, we don't need to also record the planned new - // state in this case. - }, - }, - }) + var state *states.ResourceInstanceObject - return seq + switch op { + case walkPlan, walkPlanDestroy: + + readStateDeposed := &EvalReadStateDeposed{ + Addr: addr.Resource, + Output: &state, + Key: n.DeposedKey, + Provider: &provider, + ProviderSchema: &providerSchema, + } + _, err = readStateDeposed.Eval(ctx) + if err != nil { + return err + } + + diffDestroy := &EvalDiffDestroy{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + DeposedKey: n.DeposedKey, + State: &state, + Output: &change, + } + _, err = diffDestroy.Eval(ctx) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr.Resource, + DeposedKey: n.DeposedKey, + ProviderSchema: &providerSchema, + Change: &change, + } + _, err = writeDiff.Eval(ctx) + if err != nil { + return err + } + } + return nil } // NodeDestroyDeposedResourceInstanceObject represents deposed resource @@ -133,7 +135,7 @@ var ( _ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeExecutable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) ) @@ -181,74 +183,98 @@ func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v b return nil } -// GraphNodeEvalable impl. -func (n *NodeDestroyDeposedResourceInstanceObject) EvalTree() EvalNode { - addr := n.ResourceInstanceAddr() +// GraphNodeExecutable impl. +func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error { + addr := n.ResourceInstanceAddr().Resource - var provider providers.Interface - var providerSchema *ProviderSchema var state *states.ResourceInstanceObject var change *plans.ResourceInstanceChange - var err error + var applyError error - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadStateDeposed{ - Addr: addr.Resource, - Output: &state, - Key: n.DeposedKey, - Provider: &provider, - ProviderSchema: &providerSchema, - }, - &EvalDiffDestroy{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - State: &state, - Output: &change, - }, - // Call pre-apply hook - &EvalApplyPre{ - Addr: addr.Resource, - State: &state, - Change: &change, - }, - &EvalApply{ - Addr: addr.Resource, - Config: nil, // No configuration because we are destroying - State: &state, - Change: &change, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - Output: &state, - Error: &err, - }, - // Always write the resource back to the state deposed... if it - // was successfully destroyed it will be pruned. If it was not, it will - // be caught on the next run. - &EvalWriteStateDeposed{ - Addr: addr.Resource, - Key: n.DeposedKey, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - }, - &EvalApplyPost{ - Addr: addr.Resource, - State: &state, - Error: &err, - }, - &EvalReturnError{ - Error: &err, - }, - &EvalUpdateStateHook{}, - }, + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err } + + readStateDeposed := &EvalReadStateDeposed{ + Addr: addr, + Output: &state, + Key: n.DeposedKey, + Provider: &provider, + ProviderSchema: &providerSchema, + } + _, err = readStateDeposed.Eval(ctx) + if err != nil { + return err + } + + diffDestroy := &EvalDiffDestroy{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + State: &state, + Output: &change, + } + _, err = diffDestroy.Eval(ctx) + if err != nil { + return err + } + + // Call pre-apply hook + applyPre := &EvalApplyPre{ + Addr: addr, + State: &state, + Change: &change, + } + _, err = applyPre.Eval(ctx) + if err != nil { + return err + } + + apply := &EvalApply{ + Addr: addr, + Config: nil, // No configuration because we are destroying + State: &state, + Change: &change, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + Output: &state, + Error: &applyError, + } + _, err = apply.Eval(ctx) + if err != nil { + return err + } + + // Always write the resource back to the state deposed. If it + // was successfully destroyed it will be pruned. If it was not, it will + // be caught on the next run. + writeStateDeposed := &EvalWriteStateDeposed{ + Addr: addr, + Key: n.DeposedKey, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + } + _, err = writeStateDeposed.Eval(ctx) + if err != nil { + return err + } + + applyPost := &EvalApplyPost{ + Addr: addr, + State: &state, + Error: &applyError, + } + _, err = applyPost.Eval(ctx) + if err != nil { + return err + } + if applyError != nil { + return applyError + } + UpdateStateHook(ctx) + return nil } // GraphNodeDeposer is an optional interface implemented by graph nodes that diff --git a/terraform/node_resource_destroy_deposed_test.go b/terraform/node_resource_destroy_deposed_test.go new file mode 100644 index 000000000..584dab5ac --- /dev/null +++ b/terraform/node_resource_destroy_deposed_test.go @@ -0,0 +1,130 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/states" + "github.com/zclconf/go-cty/cty" +) + +func TestNodePlanDeposedResourceInstanceObject_Execute(t *testing.T) { + deposedKey := states.NewDeposedKey() + state := states.NewState() + absResource := mustResourceInstanceAddr("test_instance.foo") + state.Module(addrs.RootModuleInstance).SetResourceInstanceDeposed( + absResource.Resource, + deposedKey, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectTainted, + AttrsJSON: []byte(`{"id":"bar"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + p := testProvider("test") + p.UpgradeResourceStateResponse = providers.UpgradeResourceStateResponse{ + UpgradedState: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + } + ctx := &MockEvalContext{ + StateState: state.SyncWrapper(), + ProviderProvider: p, + ProviderSchemaSchema: &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }, + ChangesChanges: plans.NewChanges().SyncWrapper(), + } + + node := NodePlanDeposedResourceInstanceObject{ + NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ + Addr: absResource, + NodeAbstractResource: NodeAbstractResource{ + ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + }, + }, + DeposedKey: deposedKey, + } + err := node.Execute(ctx, walkPlan) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + change := ctx.Changes().GetResourceInstanceChange(absResource, deposedKey) + if change.ChangeSrc.Action != plans.Delete { + t.Fatalf("delete change not planned") + } + +} + +func TestNodeDestroyDeposedResourceInstanceObject_Execute(t *testing.T) { + deposedKey := states.NewDeposedKey() + state := states.NewState() + absResource := mustResourceInstanceAddr("test_instance.foo") + state.Module(addrs.RootModuleInstance).SetResourceInstanceDeposed( + absResource.Resource, + deposedKey, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectTainted, + AttrsJSON: []byte(`{"id":"bar"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + p := testProvider("test") + p.UpgradeResourceStateResponse = providers.UpgradeResourceStateResponse{ + UpgradedState: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + } + ctx := &MockEvalContext{ + StateState: state.SyncWrapper(), + ProviderProvider: p, + ProviderSchemaSchema: &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }, + ChangesChanges: plans.NewChanges().SyncWrapper(), + } + + node := NodeDestroyDeposedResourceInstanceObject{ + NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ + Addr: absResource, + NodeAbstractResource: NodeAbstractResource{ + ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + }, + }, + DeposedKey: deposedKey, + } + err := node.Execute(ctx, walkApply) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !state.Empty() { + t.Fatalf("resources left in state after destroy") + } +} diff --git a/terraform/node_resource_plan_destroy.go b/terraform/node_resource_plan_destroy.go index d53c1441c..0118815bd 100644 --- a/terraform/node_resource_plan_destroy.go +++ b/terraform/node_resource_plan_destroy.go @@ -1,12 +1,8 @@ package terraform import ( - "fmt" - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" ) @@ -25,7 +21,7 @@ var ( _ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil) - _ GraphNodeEvalable = (*NodePlanDestroyableResourceInstance)(nil) + _ GraphNodeExecutable = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil) ) @@ -36,53 +32,46 @@ func (n *NodePlanDestroyableResourceInstance) DestroyAddr() *addrs.AbsResourceIn } // GraphNodeEvalable -func (n *NodePlanDestroyableResourceInstance) EvalTree() EvalNode { +func (n *NodePlanDestroyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error { addr := n.ResourceInstanceAddr() // Declare a bunch of variables that are used for state during // evaluation. These are written to by address in the EvalNodes we // declare below. - var provider providers.Interface - var providerSchema *ProviderSchema var change *plans.ResourceInstanceChange var state *states.ResourceInstanceObject - if n.ResolvedProvider.Provider.Type == "" { - // Should never happen; indicates that the graph was not constructed - // correctly since we didn't get our provider attached. - panic(fmt.Sprintf("%T %q was not assigned a resolved provider", n, dag.VertexName(n))) + _, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err } - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadState{ - Addr: addr.Resource, - Provider: &provider, - ProviderSchema: &providerSchema, - - Output: &state, - }, - &EvalDiffDestroy{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - State: &state, - Output: &change, - }, - &EvalCheckPreventDestroy{ - Addr: addr.Resource, - Config: n.Config, - Change: &change, - }, - &EvalWriteDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &change, - }, - }, + state, err = n.ReadResourceInstanceState(ctx, addr) + if err != nil { + return err } + + diffDestroy := &EvalDiffDestroy{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + State: &state, + Output: &change, + } + _, err = diffDestroy.Eval(ctx) + if err != nil { + return err + } + + err = n.checkPreventDestroy(change) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr.Resource, + ProviderSchema: &providerSchema, + Change: &change, + } + _, err = writeDiff.Eval(ctx) + return err } diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go index 2605b6f14..cca60dcac 100644 --- a/terraform/node_resource_plan_instance.go +++ b/terraform/node_resource_plan_instance.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/hashicorp/terraform/plans" - "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/addrs" @@ -27,183 +26,211 @@ var ( _ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil) - _ GraphNodeEvalable = (*NodePlannableResourceInstance)(nil) + _ GraphNodeExecutable = (*NodePlannableResourceInstance)(nil) ) // GraphNodeEvalable -func (n *NodePlannableResourceInstance) EvalTree() EvalNode { +func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) error { addr := n.ResourceInstanceAddr() // Eval info is different depending on what kind of resource this is switch addr.Resource.Resource.Mode { case addrs.ManagedResourceMode: - return n.evalTreeManagedResource(addr) + return n.managedResourceExecute(ctx, n.skipRefresh) case addrs.DataResourceMode: - return n.evalTreeDataResource(addr) + return n.dataResourceExecute(ctx) default: panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) } } -func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance) EvalNode { +func (n *NodePlannableResourceInstance) dataResourceExecute(ctx EvalContext) error { config := n.Config - var provider providers.Interface - var providerSchema *ProviderSchema + addr := n.ResourceInstanceAddr() + var change *plans.ResourceInstanceChange var state *states.ResourceInstanceObject - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err + } - &EvalReadState{ - Addr: addr.Resource, - Provider: &provider, - ProviderSchema: &providerSchema, - Output: &state, - }, + state, err = n.ReadResourceInstanceState(ctx, addr) + if err != nil { + return err + } - &EvalValidateSelfRef{ - Addr: addr.Resource, - Config: config.Config, - ProviderSchema: &providerSchema, - }, + validateSelfRef := &EvalValidateSelfRef{ + Addr: addr.Resource, + Config: config.Config, + ProviderSchema: &providerSchema, + } + _, err = validateSelfRef.Eval(ctx) + if err != nil { + return err + } - &evalReadDataPlan{ - evalReadData: evalReadData{ - Addr: addr.Resource, - Config: n.Config, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - OutputChange: &change, - State: &state, - dependsOn: n.dependsOn, - }, - }, - - // write the data source into both the refresh state and the - // working state - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - targetState: refreshState, - }, - - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - }, - - &EvalWriteDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &change, - }, + readDataPlan := &evalReadDataPlan{ + evalReadData: evalReadData{ + Addr: addr.Resource, + Config: n.Config, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + OutputChange: &change, + State: &state, + dependsOn: n.dependsOn, }, } + _, err = readDataPlan.Eval(ctx) + if err != nil { + return err + } + + // write the data source into both the refresh state and the + // working state + writeRefreshState := &EvalWriteState{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + targetState: refreshState, + } + _, err = writeRefreshState.Eval(ctx) + if err != nil { + return err + } + + writeState := &EvalWriteState{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + } + _, err = writeState.Eval(ctx) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr.Resource, + ProviderSchema: &providerSchema, + Change: &change, + } + _, err = writeDiff.Eval(ctx) + return err } -func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance) EvalNode { +func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext, skipRefresh bool) error { config := n.Config - var provider providers.Interface - var providerSchema *ProviderSchema + addr := n.ResourceInstanceAddr() + var change *plans.ResourceInstanceChange var instanceRefreshState *states.ResourceInstanceObject var instancePlanState *states.ResourceInstanceObject - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - - &EvalValidateSelfRef{ - Addr: addr.Resource, - Config: config.Config, - ProviderSchema: &providerSchema, - }, - - &EvalIf{ - If: func(ctx EvalContext) (bool, error) { - return !n.skipRefresh, nil - }, - Then: &EvalSequence{ - Nodes: []EvalNode{ - // Refresh the instance - &EvalReadState{ - Addr: addr.Resource, - Provider: &provider, - ProviderSchema: &providerSchema, - Output: &instanceRefreshState, - }, - &EvalRefreshLifecycle{ - Addr: addr, - Config: n.Config, - State: &instanceRefreshState, - ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy, - }, - &EvalRefresh{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - Provider: &provider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - State: &instanceRefreshState, - Output: &instanceRefreshState, - }, - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - State: &instanceRefreshState, - ProviderSchema: &providerSchema, - targetState: refreshState, - Dependencies: &n.Dependencies, - }, - }, - }, - }, - - // Plan the instance - &EvalDiff{ - Addr: addr.Resource, - Config: n.Config, - CreateBeforeDestroy: n.ForceCreateBeforeDestroy, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderMetas: n.ProviderMetas, - ProviderSchema: &providerSchema, - State: &instanceRefreshState, - OutputChange: &change, - OutputState: &instancePlanState, - }, - &EvalCheckPreventDestroy{ - Addr: addr.Resource, - Config: n.Config, - Change: &change, - }, - &EvalWriteState{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - State: &instancePlanState, - ProviderSchema: &providerSchema, - }, - &EvalWriteDiff{ - Addr: addr.Resource, - ProviderSchema: &providerSchema, - Change: &change, - }, - }, + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err } + + validateSelfRef := &EvalValidateSelfRef{ + Addr: addr.Resource, + Config: config.Config, + ProviderSchema: &providerSchema, + } + _, err = validateSelfRef.Eval(ctx) + if err != nil { + return err + } + + // Refresh, maybe + if !skipRefresh { + instanceRefreshState, err = n.ReadResourceInstanceState(ctx, addr) + if err != nil { + return err + } + + refreshLifecycle := &EvalRefreshLifecycle{ + Addr: addr, + Config: n.Config, + State: &instanceRefreshState, + ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy, + } + _, err = refreshLifecycle.Eval(ctx) + if err != nil { + return err + } + + refresh := &EvalRefresh{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + Provider: &provider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + State: &instanceRefreshState, + Output: &instanceRefreshState, + } + _, err = refresh.Eval(ctx) + if err != nil { + return err + } + + writeRefreshState := &EvalWriteState{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &instanceRefreshState, + targetState: refreshState, + Dependencies: &n.Dependencies, + } + _, err = writeRefreshState.Eval(ctx) + if err != nil { + return err + } + } + + // Plan the instance + diff := &EvalDiff{ + Addr: addr.Resource, + Config: n.Config, + CreateBeforeDestroy: n.ForceCreateBeforeDestroy, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderMetas: n.ProviderMetas, + ProviderSchema: &providerSchema, + State: &instanceRefreshState, + OutputChange: &change, + OutputState: &instancePlanState, + } + _, err = diff.Eval(ctx) + if err != nil { + return err + } + + err = n.checkPreventDestroy(change) + if err != nil { + return err + } + + writeState := &EvalWriteState{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + State: &instancePlanState, + ProviderSchema: &providerSchema, + } + _, err = writeState.Eval(ctx) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr.Resource, + ProviderSchema: &providerSchema, + Change: &change, + } + _, err = writeDiff.Eval(ctx) + return err } diff --git a/terraform/node_resource_plan_orphan.go b/terraform/node_resource_plan_orphan.go index 1580be829..ca0200054 100644 --- a/terraform/node_resource_plan_orphan.go +++ b/terraform/node_resource_plan_orphan.go @@ -57,7 +57,7 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp return err } - err = n.CheckPreventDestroy(addr, change) + err = n.checkPreventDestroy(change) if err != nil { return err }