Merge pull request #26413 from hashicorp/mildwonkey/the-rest-of-the-owl-tree
The Last Chapter in our Epic Saga: `EvalTree()` Refactor
This commit is contained in:
commit
c258e8efbb
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -781,49 +781,6 @@ func (n *EvalReduceDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, nil
|
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
|
// EvalWriteDiff is an EvalNode implementation that saves a planned change
|
||||||
// for an instance object into the set of global planned changes.
|
// for an instance object into the set of global planned changes.
|
||||||
type EvalWriteDiff struct {
|
type EvalWriteDiff struct {
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
// EvalReturnError is an EvalNode implementation that returns an
|
// EvalEarlyExitError is a special error return value that can be returned
|
||||||
// error if it is present.
|
// by eval nodes that does an early exit.
|
||||||
//
|
type EvalEarlyExitError struct{}
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *EvalReturnError) Eval(ctx EvalContext) (interface{}, error) {
|
func (EvalEarlyExitError) Error() string { return "early exit" }
|
||||||
if n.Error == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, *n.Error
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -60,42 +60,3 @@ func GetProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Inter
|
||||||
schema := ctx.ProviderSchema(addr)
|
schema := ctx.ProviderSchema(addr)
|
||||||
return provider, schema, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuildProviderConfig(t *testing.T) {
|
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)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package terraform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEvalSequence_impl(t *testing.T) {
|
|
||||||
var _ EvalNodeFilterable = new(EvalSequence)
|
|
||||||
}
|
|
|
@ -149,35 +149,7 @@ func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return obj, nil
|
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.
|
// 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 {
|
func UpdateStateHook(ctx EvalContext) error {
|
||||||
// In principle we could grab the lock here just long enough to take a
|
// 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
|
// deep copy and then pass that to our hooks below, but we'll instead
|
||||||
|
|
|
@ -12,29 +12,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/states"
|
"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) {
|
func TestEvalReadState(t *testing.T) {
|
||||||
var output *states.ResourceInstanceObject
|
var output *states.ResourceInstanceObject
|
||||||
mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
|
mockProvider := mockProviderWithResourceTypeSchema("aws_instance", &configschema.Block{
|
||||||
|
@ -50,7 +27,7 @@ func TestEvalReadState(t *testing.T) {
|
||||||
|
|
||||||
cases := map[string]struct {
|
cases := map[string]struct {
|
||||||
Resources map[string]*ResourceState
|
Resources map[string]*ResourceState
|
||||||
Node EvalNode
|
Node *EvalReadState
|
||||||
ExpectedInstanceId string
|
ExpectedInstanceId string
|
||||||
}{
|
}{
|
||||||
"ReadState gets primary instance state": {
|
"ReadState gets primary instance state": {
|
||||||
|
@ -74,6 +51,59 @@ func TestEvalReadState(t *testing.T) {
|
||||||
},
|
},
|
||||||
ExpectedInstanceId: "i-abc123",
|
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": {
|
"ReadStateDeposed gets deposed instance": {
|
||||||
Resources: map[string]*ResourceState{
|
Resources: map[string]*ResourceState{
|
||||||
"aws_instance.bar": &ResourceState{
|
"aws_instance.bar": &ResourceState{
|
||||||
|
@ -97,7 +127,6 @@ func TestEvalReadState(t *testing.T) {
|
||||||
ExpectedInstanceId: "i-abc123",
|
ExpectedInstanceId: "i-abc123",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, c := range cases {
|
for k, c := range cases {
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
ctx := new(MockEvalContext)
|
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) {
|
func TestUpdateStateHook(t *testing.T) {
|
||||||
mockHook := new(MockHook)
|
mockHook := new(MockHook)
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"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 the node is exec-able, then execute it.
|
||||||
if ev, ok := v.(GraphNodeExecutable); ok {
|
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))
|
diags = diags.Append(walker.Execute(vertexCtx, ev))
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return
|
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 the node is dynamically expanded, then expand it
|
||||||
if ev, ok := v.(GraphNodeDynamicExpandable); ok {
|
if ev, ok := v.(GraphNodeDynamicExpandable); ok {
|
||||||
log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v))
|
log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v))
|
||||||
|
|
|
@ -2,7 +2,6 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -12,8 +11,6 @@ type GraphWalker interface {
|
||||||
EvalContext() EvalContext
|
EvalContext() EvalContext
|
||||||
EnterPath(addrs.ModuleInstance) EvalContext
|
EnterPath(addrs.ModuleInstance) EvalContext
|
||||||
ExitPath(addrs.ModuleInstance)
|
ExitPath(addrs.ModuleInstance)
|
||||||
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
|
|
||||||
ExitEvalTree(dag.Vertex, interface{}, error) tfdiags.Diagnostics
|
|
||||||
Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics
|
Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +19,7 @@ type GraphWalker interface {
|
||||||
// implementing all the required functions.
|
// implementing all the required functions.
|
||||||
type NullGraphWalker struct{}
|
type NullGraphWalker struct{}
|
||||||
|
|
||||||
func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) }
|
func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) }
|
||||||
func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) }
|
func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) }
|
||||||
func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {}
|
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) Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics { return nil }
|
func (NullGraphWalker) Execute(EvalContext, GraphNodeExecutable) tfdiags.Diagnostics { return nil }
|
||||||
|
|
|
@ -2,14 +2,12 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/instances"
|
"github.com/hashicorp/terraform/instances"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
"github.com/hashicorp/terraform/providers"
|
||||||
|
@ -106,49 +104,6 @@ func (w *ContextGraphWalker) EvalContext() EvalContext {
|
||||||
return ctx
|
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() {
|
func (w *ContextGraphWalker) init() {
|
||||||
w.contexts = make(map[string]*BuiltinEvalContext)
|
w.contexts = make(map[string]*BuiltinEvalContext)
|
||||||
w.providerCache = make(map[string]providers.Interface)
|
w.providerCache = make(map[string]providers.Interface)
|
||||||
|
|
|
@ -498,6 +498,8 @@ func (n *NodeAbstractResource) WriteResourceState(ctx EvalContext, addr addrs.Ab
|
||||||
return nil
|
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) {
|
func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, error) {
|
||||||
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
|
|
||||||
|
@ -540,12 +542,40 @@ func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr a
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPreventDestroy returns an error if a resource has PreventDestroy
|
// ReadDiff returns the planned change for a particular resource instance
|
||||||
// configured and the diff would destroy the resource.
|
// object.
|
||||||
func (n *NodeAbstractResource) CheckPreventDestroy(addr addrs.AbsResourceInstance, change *plans.ResourceInstanceChange) error {
|
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 {
|
if change == nil || n.Config == nil || n.Config.Managed == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
preventDestroy := n.Config.Managed.PreventDestroy
|
preventDestroy := n.Config.Managed.PreventDestroy
|
||||||
|
|
||||||
if (change.Action == plans.Delete || change.Action.IsReplace()) && 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",
|
Summary: "Instance cannot be destroyed",
|
||||||
Detail: fmt.Sprintf(
|
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.",
|
"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,
|
Subject: &n.Config.DeclRange,
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
@ -34,7 +33,7 @@ var (
|
||||||
_ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil)
|
_ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil)
|
||||||
_ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil)
|
_ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil)
|
||||||
_ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil)
|
_ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil)
|
||||||
_ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil)
|
_ GraphNodeExecutable = (*NodeApplyableResourceInstance)(nil)
|
||||||
_ GraphNodeAttachDependencies = (*NodeApplyableResourceInstance)(nil)
|
_ GraphNodeAttachDependencies = (*NodeApplyableResourceInstance)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,8 +100,8 @@ func (n *NodeApplyableResourceInstance) AttachDependencies(deps []addrs.ConfigRe
|
||||||
n.Dependencies = deps
|
n.Dependencies = deps
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeExecutable
|
||||||
func (n *NodeApplyableResourceInstance) EvalTree() EvalNode {
|
func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
addr := n.ResourceInstanceAddr()
|
addr := n.ResourceInstanceAddr()
|
||||||
|
|
||||||
if n.Config == nil {
|
if n.Config == nil {
|
||||||
|
@ -120,319 +119,334 @@ func (n *NodeApplyableResourceInstance) EvalTree() EvalNode {
|
||||||
addr,
|
addr,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
err := diags.Err()
|
return diags.Err()
|
||||||
return &EvalReturnError{
|
|
||||||
Error: &err,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eval info is different depending on what kind of resource this is
|
// Eval info is different depending on what kind of resource this is
|
||||||
switch n.Config.Mode {
|
switch n.Config.Mode {
|
||||||
case addrs.ManagedResourceMode:
|
case addrs.ManagedResourceMode:
|
||||||
return n.evalTreeManagedResource(addr)
|
return n.managedResourceExecute(ctx)
|
||||||
case addrs.DataResourceMode:
|
case addrs.DataResourceMode:
|
||||||
return n.evalTreeDataResource(addr)
|
return n.dataResourceExecute(ctx)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
|
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance) EvalNode {
|
func (n *NodeApplyableResourceInstance) dataResourceExecute(ctx EvalContext) error {
|
||||||
var provider providers.Interface
|
addr := n.ResourceInstanceAddr().Resource
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var change *plans.ResourceInstanceChange
|
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
|
var state *states.ResourceInstanceObject
|
||||||
|
readDataApply := &evalReadDataApply{
|
||||||
return &EvalSequence{
|
evalReadData{
|
||||||
Nodes: []EvalNode{
|
Addr: addr,
|
||||||
&EvalGetProvider{
|
Config: n.Config,
|
||||||
Addr: n.ResolvedProvider,
|
Planned: &change,
|
||||||
Output: &provider,
|
Provider: &provider,
|
||||||
Schema: &providerSchema,
|
ProviderAddr: n.ResolvedProvider,
|
||||||
},
|
ProviderMetas: n.ProviderMetas,
|
||||||
|
ProviderSchema: &providerSchema,
|
||||||
// Get the saved diff for apply
|
State: &state,
|
||||||
&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{},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_, 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
|
// Declare a bunch of variables that are used for state during
|
||||||
// evaluation. Most of this are written to by-address below.
|
// 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 state *states.ResourceInstanceObject
|
||||||
var err error
|
|
||||||
var createNew bool
|
var createNew bool
|
||||||
var createBeforeDestroyEnabled bool
|
var createBeforeDestroyEnabled bool
|
||||||
var deposedKey states.DeposedKey
|
var deposedKey states.DeposedKey
|
||||||
|
|
||||||
return &EvalSequence{
|
addr := n.ResourceInstanceAddr().Resource
|
||||||
Nodes: []EvalNode{
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
&EvalGetProvider{
|
if err != nil {
|
||||||
Addr: n.ResolvedProvider,
|
return err
|
||||||
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{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,12 +143,7 @@ func (n *NodeDestroyResourceInstance) Execute(ctx EvalContext, op walkOperation)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
evalReadDiff := &EvalReadDiff{
|
changeApply, err = n.ReadDiff(ctx, providerSchema)
|
||||||
Addr: addr.Resource,
|
|
||||||
ProviderSchema: &providerSchema,
|
|
||||||
Change: &changeApply,
|
|
||||||
}
|
|
||||||
_, err = evalReadDiff.Eval(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ var (
|
||||||
_ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeExecutable = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
_ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
|
||||||
)
|
)
|
||||||
|
@ -64,55 +63,58 @@ func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable impl.
|
// GraphNodeEvalable impl.
|
||||||
func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode {
|
func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
addr := n.ResourceInstanceAddr()
|
addr := n.ResourceInstanceAddr()
|
||||||
|
|
||||||
var provider providers.Interface
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
var providerSchema *ProviderSchema
|
if err != nil {
|
||||||
var state *states.ResourceInstanceObject
|
return err
|
||||||
|
}
|
||||||
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
|
|
||||||
|
|
||||||
// During the plan walk we always produce a planned destroy change, because
|
// During the plan walk we always produce a planned destroy change, because
|
||||||
// destroying is the only supported action for deposed objects.
|
// destroying is the only supported action for deposed objects.
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
|
var state *states.ResourceInstanceObject
|
||||||
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.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
// NodeDestroyDeposedResourceInstanceObject represents deposed resource
|
||||||
|
@ -133,7 +135,7 @@ var (
|
||||||
_ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeExecutable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
_ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
_ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
|
||||||
)
|
)
|
||||||
|
@ -181,74 +183,98 @@ func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v b
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable impl.
|
// GraphNodeExecutable impl.
|
||||||
func (n *NodeDestroyDeposedResourceInstanceObject) EvalTree() EvalNode {
|
func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
addr := n.ResourceInstanceAddr()
|
addr := n.ResourceInstanceAddr().Resource
|
||||||
|
|
||||||
var provider providers.Interface
|
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var state *states.ResourceInstanceObject
|
var state *states.ResourceInstanceObject
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
var err error
|
var applyError error
|
||||||
|
|
||||||
return &EvalSequence{
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
Nodes: []EvalNode{
|
if err != nil {
|
||||||
&EvalGetProvider{
|
return err
|
||||||
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{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// GraphNodeDeposer is an optional interface implemented by graph nodes that
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,8 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,7 +21,7 @@ var (
|
||||||
_ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil)
|
_ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil)
|
||||||
_ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil)
|
_ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil)
|
||||||
_ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil)
|
_ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil)
|
||||||
_ GraphNodeEvalable = (*NodePlanDestroyableResourceInstance)(nil)
|
_ GraphNodeExecutable = (*NodePlanDestroyableResourceInstance)(nil)
|
||||||
_ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil)
|
_ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,53 +32,46 @@ func (n *NodePlanDestroyableResourceInstance) DestroyAddr() *addrs.AbsResourceIn
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeEvalable
|
||||||
func (n *NodePlanDestroyableResourceInstance) EvalTree() EvalNode {
|
func (n *NodePlanDestroyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
addr := n.ResourceInstanceAddr()
|
addr := n.ResourceInstanceAddr()
|
||||||
|
|
||||||
// Declare a bunch of variables that are used for state during
|
// Declare a bunch of variables that are used for state during
|
||||||
// evaluation. These are written to by address in the EvalNodes we
|
// evaluation. These are written to by address in the EvalNodes we
|
||||||
// declare below.
|
// declare below.
|
||||||
var provider providers.Interface
|
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
var state *states.ResourceInstanceObject
|
var state *states.ResourceInstanceObject
|
||||||
|
|
||||||
if n.ResolvedProvider.Provider.Type == "" {
|
_, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
// Should never happen; indicates that the graph was not constructed
|
if err != nil {
|
||||||
// correctly since we didn't get our provider attached.
|
return err
|
||||||
panic(fmt.Sprintf("%T %q was not assigned a resolved provider", n, dag.VertexName(n)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &EvalSequence{
|
state, err = n.ReadResourceInstanceState(ctx, addr)
|
||||||
Nodes: []EvalNode{
|
if err != nil {
|
||||||
&EvalGetProvider{
|
return err
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/plans"
|
"github.com/hashicorp/terraform/plans"
|
||||||
"github.com/hashicorp/terraform/providers"
|
|
||||||
"github.com/hashicorp/terraform/states"
|
"github.com/hashicorp/terraform/states"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
@ -27,183 +26,211 @@ var (
|
||||||
_ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil)
|
_ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil)
|
||||||
_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
|
_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
|
||||||
_ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil)
|
_ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil)
|
||||||
_ GraphNodeEvalable = (*NodePlannableResourceInstance)(nil)
|
_ GraphNodeExecutable = (*NodePlannableResourceInstance)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphNodeEvalable
|
// GraphNodeEvalable
|
||||||
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
|
func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
|
||||||
addr := n.ResourceInstanceAddr()
|
addr := n.ResourceInstanceAddr()
|
||||||
|
|
||||||
// Eval info is different depending on what kind of resource this is
|
// Eval info is different depending on what kind of resource this is
|
||||||
switch addr.Resource.Resource.Mode {
|
switch addr.Resource.Resource.Mode {
|
||||||
case addrs.ManagedResourceMode:
|
case addrs.ManagedResourceMode:
|
||||||
return n.evalTreeManagedResource(addr)
|
return n.managedResourceExecute(ctx, n.skipRefresh)
|
||||||
case addrs.DataResourceMode:
|
case addrs.DataResourceMode:
|
||||||
return n.evalTreeDataResource(addr)
|
return n.dataResourceExecute(ctx)
|
||||||
default:
|
default:
|
||||||
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
|
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
|
config := n.Config
|
||||||
var provider providers.Interface
|
addr := n.ResourceInstanceAddr()
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
var state *states.ResourceInstanceObject
|
var state *states.ResourceInstanceObject
|
||||||
|
|
||||||
return &EvalSequence{
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
Nodes: []EvalNode{
|
if err != nil {
|
||||||
&EvalGetProvider{
|
return err
|
||||||
Addr: n.ResolvedProvider,
|
}
|
||||||
Output: &provider,
|
|
||||||
Schema: &providerSchema,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalReadState{
|
state, err = n.ReadResourceInstanceState(ctx, addr)
|
||||||
Addr: addr.Resource,
|
if err != nil {
|
||||||
Provider: &provider,
|
return err
|
||||||
ProviderSchema: &providerSchema,
|
}
|
||||||
Output: &state,
|
|
||||||
},
|
|
||||||
|
|
||||||
&EvalValidateSelfRef{
|
validateSelfRef := &EvalValidateSelfRef{
|
||||||
Addr: addr.Resource,
|
Addr: addr.Resource,
|
||||||
Config: config.Config,
|
Config: config.Config,
|
||||||
ProviderSchema: &providerSchema,
|
ProviderSchema: &providerSchema,
|
||||||
},
|
}
|
||||||
|
_, err = validateSelfRef.Eval(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
&evalReadDataPlan{
|
readDataPlan := &evalReadDataPlan{
|
||||||
evalReadData: evalReadData{
|
evalReadData: evalReadData{
|
||||||
Addr: addr.Resource,
|
Addr: addr.Resource,
|
||||||
Config: n.Config,
|
Config: n.Config,
|
||||||
Provider: &provider,
|
Provider: &provider,
|
||||||
ProviderAddr: n.ResolvedProvider,
|
ProviderAddr: n.ResolvedProvider,
|
||||||
ProviderMetas: n.ProviderMetas,
|
ProviderMetas: n.ProviderMetas,
|
||||||
ProviderSchema: &providerSchema,
|
ProviderSchema: &providerSchema,
|
||||||
OutputChange: &change,
|
OutputChange: &change,
|
||||||
State: &state,
|
State: &state,
|
||||||
dependsOn: n.dependsOn,
|
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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
_, 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
|
config := n.Config
|
||||||
var provider providers.Interface
|
addr := n.ResourceInstanceAddr()
|
||||||
var providerSchema *ProviderSchema
|
|
||||||
var change *plans.ResourceInstanceChange
|
var change *plans.ResourceInstanceChange
|
||||||
var instanceRefreshState *states.ResourceInstanceObject
|
var instanceRefreshState *states.ResourceInstanceObject
|
||||||
var instancePlanState *states.ResourceInstanceObject
|
var instancePlanState *states.ResourceInstanceObject
|
||||||
|
|
||||||
return &EvalSequence{
|
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
|
||||||
Nodes: []EvalNode{
|
if err != nil {
|
||||||
&EvalGetProvider{
|
return err
|
||||||
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = n.CheckPreventDestroy(addr, change)
|
err = n.checkPreventDestroy(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue